1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring-Security & JWT 實現(xiàn) token

        共 16387字,需瀏覽 33分鐘

         ·

        2022-07-27 02:59


        一、JWT

        //www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

        二、項目環(huán)境搭建

        2.1 引入依賴

        pom.xml

        <dependencies>        <dependency>            <groupId>javax.xml.bind</groupId>            <artifactId>jaxb-api</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>com.sun.xml.bind</groupId>            <artifactId>jaxb-impl</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>com.sun.xml.bind</groupId>            <artifactId>jaxb-core</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>javax.activation</groupId>            <artifactId>activation</artifactId>            <version>1.1.1</version>        </dependency>
        <!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
        <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
        <!-- mybatisplus與springboot整合 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- mybatis plus 代碼生成器依賴 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.2.0</version> </dependency>
        <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
        <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- mysql驅(qū)動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency>
        </dependencies>
        <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
        </project>

        主要是要引入Spring Secruity和jwt的依賴。

        2.2 實體類(User)

        User.java

        public class User implements Serializable {    @TableId(value = "user_id",type= IdType.AUTO)    private int userId;    private String userName;    private String Password;    private String userAge;    private String Role;
        public int getUserId() { return userId; }
        public void setUserId(int userId) { this.userId = userId; }
        public String getUserName() { return userName; }
        public void setUserName(String userName) { this.userName = userName; }
        public String getPassword() { return Password; }
        public void setPassword(String password) { Password = password; }
        public String getUserAge() { return userAge; }
        public void setUserAge(String userAge) { this.userAge = userAge; }
        public String getRole() { return Role; }
        public void setRole(String role) { Role = role; }}

        2.3 jwt工具類

        JwtTokenUtils.java

        public class JwtTokenUtils {
        public static final String TOKEN_HEADER = "token"; public static final String TOKEN_PREFIX = "";
        private static final String SECRET = "jwtsecretdemo"; private static final String ISS = "echisan";
            // 過期時間是3600秒,即是1個小時 private static final long EXPIRATION = 3600L;
        // 選擇了記住我之后的過期時間為7天 private static final long EXPIRATION_REMEMBER = 604800L;
        // 創(chuàng)建token public static String createToken(String username, boolean isRememberMe) { long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION; return Jwts.builder() .signWith(SignatureAlgorithm.HS512, SECRET) .setIssuer(ISS) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .compact(); }
        // 從token中獲取用戶名 public static String getUsername(String token){ return getTokenBody(token).getSubject(); }
        // 是否已過期 public static boolean isExpiration(String token){ return getTokenBody(token).getExpiration().before(new Date()); }
        private static Claims getTokenBody(String token){ return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); }}

        jwt工具類,對jjwt封裝一下方便調(diào)用。

        2.4 Dao層

        因為使用的是mybatis plus,所以沒有使用mapper.xml書寫sql語句,直接調(diào)用提供的CRUD。

        UserDao.java

        public interface UserDao extends BaseMapper<User> {
        }

        2.5 ServiceImpl層

        UserDetailsServiceImpl.java

        @Servicepublic class UserDetailsServiceImpl implements UserDetailsService {    @Autowired    private UserDao userDao;
        @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { Map<String, Object> map = new HashMap<>(); map.put("user_name", s); User user = userDao.selectByMap(map).get(0); return new JwtUser(user); }}
        • 注意:這個serviceImpl實現(xiàn)的接口UserDetailsService是框架提供的。

        • 使用springSecurity需要實現(xiàn)UserDetailsService接口供權(quán)限框架調(diào)用,該方法只需要實現(xiàn)一個方法就可以了,那就是根據(jù)用戶名去獲取用戶,這里使用的是mybatis plus提供的操作接口。

        接著去實現(xiàn)一下剛才返回的UserDetails

        public class JwtUser implements UserDetails {
        private Integer id; private String username; private String password; private Collection<? extends GrantedAuthority> authorities;
        public JwtUser() { }
        // 寫一個能直接使用user創(chuàng)建jwtUser的構(gòu)造器 public JwtUser(User user) { id = user.getUserId(); username = user.getUserName(); password = user.getPassword(); authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole())); }
        // 獲取權(quán)限信息,目前博主只會拿來存角色。。 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; }
        @Override public String getPassword() { return password; }
        @Override public String getUsername() { return username; }
        // 賬號是否未過期,默認是false,記得要改一下 @Override public boolean isAccountNonExpired() { return true; }
        // 賬號是否未鎖定,默認是false,記得也要改一下 @Override public boolean isAccountNonLocked() { return true; }
        // 賬號憑證是否未過期,默認是false,記得還要改一下 @Override public boolean isCredentialsNonExpired() { return true; }
        // 這個有點抽象不會翻譯,默認也是false,記得改一下 @Override public boolean isEnabled() { return true; }
        // 我自己重寫打印下信息看的 @Override public String toString() { return "JwtUser{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", authorities=" + authorities + '}'; }}

        三、配置攔截器

        這邊需要實現(xiàn)兩個過濾器。使用JWTAuthenticationFilter去進行用戶賬號的驗證,使用JWTAuthorizationFilter去進行用戶權(quán)限的驗證。

        3.1 JWTAuthenticationFilter

        JWTAuthenticationFilter繼承于UsernamePasswordAuthenticationFilter

        該攔截器用于獲取用戶登錄的信息,只需創(chuàng)建一個token并調(diào)用authenticationManager.authenticate()讓spring-security去進行驗證就可以了,不用自己查數(shù)據(jù)庫再對比密碼了,這一步交給spring去操作。

        這個操作有點像是shiro的subject.login(new UsernamePasswordToken()),驗證的事情交給框架。

        JWTAuthenticationFilter.java

        public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        private AuthenticationManager authenticationManager;
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; }
        @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 從輸入流中獲取到登錄的信息 try { LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()) ); } catch (IOException e) { e.printStackTrace(); return null; } }
        // 成功驗證后調(diào)用的方法 // 如果驗證成功,就生成token并返回 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // 查看源代碼會發(fā)現(xiàn)調(diào)用getPrincipal()方法會返回一個實現(xiàn)了`UserDetails`接口的對象 // 所以就是JwtUser啦 JwtUser jwtUser = (JwtUser) authResult.getPrincipal(); System.out.println("jwtUser:" + jwtUser.toString()); String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false); // 返回創(chuàng)建成功的token // 但是這里創(chuàng)建的token只是單純的token // 按照jwt的規(guī)定,最后請求的格式應(yīng)該是 `Bearer token` response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token); }
        // 這是驗證失敗時候調(diào)用的方法 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { response.getWriter().write("authentication failed, reason: " + failed.getMessage()); }}

        這里還用到了LoginUser這個實體類,也是需要自己定義一下的。

        LoginUser.java

        public class LoginUser {
        private String username; private String password; private Integer rememberMe;
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
        public Integer getRememberMe() { return rememberMe; }
        public void setRememberMe(Integer rememberMe) { this.rememberMe = rememberMe; }}

        3.2 JWTAuthorizationFilter

        驗證成功當然就是進行鑒權(quán)了,每一次需要權(quán)限的請求都需要檢查該用戶是否有該權(quán)限去操作該資源,當然這也是框架幫我們做的,那么我們需要做什么呢?很簡單,只要告訴spring-security該用戶是否已登錄,是什么角色,擁有什么權(quán)限就可以了。

        JWTAuthenticationFilter繼承于BasicAuthenticationFilter,至于為什么要繼承這個我也不太清楚了,這個我也是網(wǎng)上看到的其中一種實現(xiàn),實在springSecurity苦手,不過我覺得不繼承這個也沒事呢(實現(xiàn)以下filter接口或者繼承其他filter實現(xiàn)子類也可以吧)只要確保過濾器的順序,JWTAuthorizationFilter在JWTAuthenticationFilter后面就沒問題了。

        JWTAuthorizationFilter.java

        public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
        public JWTAuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); }
        @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER); // 如果請求頭中沒有Authorization信息則直接放行了 if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) { chain.doFilter(request, response); return; } // 如果請求頭中有token,則進行解析,并且設(shè)置認證信息 SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader)); super.doFilterInternal(request, response, chain); }
        // 這里從token中獲取用戶信息并新建一個token private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) { String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, ""); String username = JwtTokenUtils.getUsername(token); if (username != null){ return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); } return null; }}

        3.3 配置SpringSecurity

        需要開啟一下注解@EnableWebSecurity然后再繼承一下WebSecurityConfigurerAdapter就可以啦

        WebSecurityConfig.java

        @Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired // 因為UserDetailsService的實現(xiàn)類實在太多啦,這里設(shè)置一下我們要注入的實現(xiàn)類 @Qualifier("userDetailsServiceImpl") private UserDetailsService userDetailsService;
        // 加密密碼的,安全第一嘛~ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); }
        @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); }
        @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .authorizeRequests() // 測試用資源,需要驗證了的用戶才能訪問 .antMatchers("/tasks/**").authenticated() // 其他都放行了 .anyRequest().permitAll() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) // 不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); }
        @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); return source; }}

        四、測試

        4.1 注冊

        AuthController.java

        @RestController@RequestMapping("/auth")public class AuthController {
        // 為了減少篇幅就不寫service接口了 @Autowired private UserDao userDao;
        @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
        @PostMapping("/register") public String registerUser(@RequestBody Map<String,String> registerUser){ User user = new User(); user.setUserName(registerUser.get("username")); // 記得注冊的時候把密碼加密一下 user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password"))); user.setRole("ROLE_USER"); int result = userDao.insert(user); return Integer.toString(result); }}

        4.2 登陸

        根據(jù)UsernamePasswordAuthenticationFilter的源代碼,可以看出登錄默認是/login

        public UsernamePasswordAuthenticationFilter() {    super(new AntPathRequestMatcher("/login", "POST"));}

        當然也可以自定義,只需要在JWTAuthenticationFilter的構(gòu)造方法中加入下面那一句話就可以啦

        public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {        this.authenticationManager = authenticationManager;        super.setFilterProcessesUrl("/auth/login");}


        4.3 接口驗證

        helloController.java

        @RestController@RequestMapping("/tasks")public class HelloController {    @GetMapping("/hello")    public String hello() {        return "hello jwt !";    }    @GetMapping("/admin")    public String admin() {        return "hello admin !";    }}

        4.4 測試結(jié)果

        • 先是注冊

        • 登陸

        這是可以獲取 token


        • 接口訪問測試

        需要將 token 加上才可以訪問成功。



        記得點「」和「在看」↓

        愛你們


        瀏覽 50
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人免费视频观看 | 国产在线视频99 | 天天干天天草 | 亚洲爆乳无码精品AAA片蜜桃 | 哪个网站可以看做爱视频 | 操无毛逼 | 国产女人18毛片水真多1kt∧ | 成人精品自拍视频 | 成人三级电影在线观看 | 四虎成人影院樱桃视频 |