日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
實戰(zhàn)篇:Security+JWT組合拳

Good morning, everyone!

之前我們已經(jīng)說過用Shiro和JWT來實現(xiàn)身份認證和用戶授權(quán),今天我們再來說一下「Security和JWT」的組合拳。

簡介

先贅述一下身份認證和用戶授權(quán):

用戶認證(Authentication):系統(tǒng)通過校驗用戶提供的用戶名和密碼來驗證該用戶是否為系統(tǒng)中的合法主體,即是否可以訪問該系統(tǒng);

用戶授權(quán)(Authorization):系統(tǒng)為用戶分配不同的角色,以獲取對應的權(quán)限,即驗證該用戶是否有權(quán)限執(zhí)行該操作;

Web應用的安全性包括用戶認證和用戶授權(quán)兩個部分,而Spring Security(以下簡稱Security)基于Spring框架,正好可以完整解決該問題。

它的真正強大之處在于它可以輕松擴展以滿足自定義要求。

原理

Security可以看做是由一組filter過濾器鏈組成的權(quán)限認證。它的整個工作流程如下所示:

圖中綠色認證方式是可以配置的,橘黃色和藍色的位置不可更改:

  • FilterSecurityInterceptor:最后的過濾器,它會決定當前的請求可不可以訪問Controller
  • ExceptionTranslationFilter:異常過濾器,接收到異常消息時會引導用戶進行認證;

實戰(zhàn)

項目準備

我們使用Spring Boot框架來集成。

1.pom文件引入的依賴

 
 
 
 
  1.  
  2.     org.springframework.boot 
  3.     spring-boot-starter 
  4.  
  5.  
  6.  
  7.     org.springframework.boot 
  8.     spring-boot-starter-web 
  9.      
  10.          
  11.             org.springframework.boot 
  12.             spring-boot-starter-tomcat 
  13.          
  14.      
  15.  
  16.  
  17.  
  18.     org.springframework.boot 
  19.     spring-boot-starter-undertow 
  20.  
  21.  
  22.  
  23.     mysql 
  24.     mysql-connector-java 
  25.  
  26.  
  27.  
  28.     com.baomidou 
  29.     mybatis-plus-boot-starter 
  30.     3.4.0 
  31.  
  32.  
  33.  
  34.     org.projectlombok 
  35.     lombok 
  36.  
  37.  
  38.  
  39.  
  40.     com.alibaba 
  41.     fastjson 
  42.     1.2.74 
  43.  
  44.  
  45.  
  46.     joda-time 
  47.     joda-time 
  48.     2.10.6 
  49.  
  50.  
  51.  
  52.     org.springframework.boot 
  53.     spring-boot-starter-test 
  54.  

2.application.yml配置

 
 
 
 
  1. spring: 
  2.   application: 
  3.     name: securityjwt 
  4.   datasource: 
  5.     driver-class-name: com.mysql.cj.jdbc.Driver 
  6.     url: jdbc:mysql://127.0.0.1:3306/cheetah?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 
  7.     username: root 
  8.     password: 123456 
  9.  
  10. server: 
  11.   port: 8080 
  12.  
  13. mybatis: 
  14.   mapper-locations: classpath:mapper/*.xml 
  15.   type-aliases-package: com.itcheetah.securityjwt.entity 
  16.   configuration: 
  17.     map-underscore-to-camel-case: true 
  18.  
  19. rsa: 
  20.   key: 
  21.     pubKeyFile: C:\Users\Desktop\jwt\id_key_rsa.pub 
  22.     priKeyFile: C:\Users\Desktop\jwt\id_key_rsa 

3.SQL文件

 
 
 
 
  1. /** 
  2. * sys_user_info 
  3. **/ 
  4.  
  5. SET NAMES utf8mb4; 
  6. SET FOREIGN_KEY_CHECKS = 0; 
  7.  
  8. -- ---------------------------- 
  9. -- Table structure for sys_user_info 
  10. -- ---------------------------- 
  11. DROP TABLE IF EXISTS `sys_user_info`; 
  12. CREATE TABLE `sys_user_info`  ( 
  13.   `id` bigint(20) NOT NULL AUTO_INCREMENT, 
  14.   `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 
  15.   `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 
  16.   PRIMARY KEY (`id`) USING BTREE 
  17. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 
  18.  
  19. SET FOREIGN_KEY_CHECKS = 1; 
  20.  
  21.  
  22. /** 
  23. * product_info 
  24. **/ 
  25.  
  26. SET NAMES utf8mb4; 
  27. SET FOREIGN_KEY_CHECKS = 0; 
  28.  
  29. -- ---------------------------- 
  30. -- Table structure for product_info 
  31. -- ---------------------------- 
  32. DROP TABLE IF EXISTS `product_info`; 
  33. CREATE TABLE `product_info`  ( 
  34.   `id` bigint(20) NOT NULL AUTO_INCREMENT, 
  35.   `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 
  36.   `price` decimal(10, 4) NULL DEFAULT NULL, 
  37.   `create_date` datetime(0) NULL DEFAULT NULL, 
  38.   `update_date` datetime(0) NULL DEFAULT NULL, 
  39.   PRIMARY KEY (`id`) USING BTREE 
  40. ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 
  41.  
  42. SET FOREIGN_KEY_CHECKS = 1; 

引入依賴

 
 
 
 
  1.  
  2.     org.springframework.boot 
  3.     spring-boot-starter-security 
  4.  
  5.  
  6.  
  7.  
  8.     io.jsonwebtoken 
  9.     jjwt 
  10.     0.9.1 
  11.  

引入之后啟動項目,會有如圖所示:

其中用戶名為user,密碼為上圖中的字符串。

SecurityConfig類

 
 
 
 
  1. //開啟全局方法安全性 
  2. @EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true) 
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
  4.  
  5.     //認證失敗處理類 
  6.     @Autowired 
  7.     private AuthenticationEntryPointImpl unauthorizedHandler; 
  8.  
  9.     //提供公鑰私鑰的配置類 
  10.     @Autowired 
  11.     private RsaKeyProperties prop; 
  12.  
  13.     @Autowired 
  14.     private UserInfoService userInfoService; 
  15.      
  16.     @Override 
  17.     protected void configure(HttpSecurity httpSecurity) throws Exception { 
  18.         httpSecurity 
  19.                 // CSRF禁用,因為不使用session 
  20.                 .csrf().disable() 
  21.                 // 認證失敗處理類 
  22.                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() 
  23.                 // 基于token,所以不需要session 
  24.                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 
  25.                 // 過濾請求 
  26.                 .authorizeRequests() 
  27.                 .antMatchers( 
  28.                         HttpMethod.GET, 
  29.                         "/*.html", 
  30.                         "/**/*.html", 
  31.                         "/**/*.css", 
  32.                         "/**/*.js" 
  33.                 ).permitAll() 
  34.                 // 除上面外的所有請求全部需要鑒權(quán)認證 
  35.                 .anyRequest().authenticated() 
  36.                 .and() 
  37.                 .headers().frameOptions().disable(); 
  38.         // 添加JWT filter 
  39.         httpSecurity.addFilter(new TokenLoginFilter(super.authenticationManager(), prop)) 
  40.                 .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop)); 
  41.     } 
  42.  
  43.     //指定認證對象的來源 
  44.     public void configure(AuthenticationManagerBuilder auth) throws Exception { 
  45.          
  46.         auth.userDetailsService(userInfoService) 
  47.         //從前端傳遞過來的密碼就會被加密,所以從數(shù)據(jù)庫 
  48.         //查詢到的密碼必須是經(jīng)過加密的,而這個過程都是 
  49.         //在用戶注冊的時候進行加密的。 
  50.         .passwordEncoder(passwordEncoder()); 
  51.     } 
  52.  
  53.     //密碼加密 
  54.     @Bean 
  55.     public BCryptPasswordEncoder passwordEncoder(){ 
  56.         return new BCryptPasswordEncoder(); 
  57.     } 

「攔截規(guī)則」

  • anyRequest:匹配所有請求路徑
  • access:SpringEl表達式結(jié)果為true時可以訪問
  • anonymous:匿名可以訪問
  • `denyAll:用戶不能訪問
  • fullyAuthenticated:用戶完全認證可以訪問(非remember-me下自動登錄)
  • hasAnyAuthority:如果有參數(shù),參數(shù)表示權(quán)限,則其中任何一個權(quán)限可以訪問
  • hasAnyRole:如果有參數(shù),參數(shù)表示角色,則其中任何一個角色可以訪問
  • hasAuthority:如果有參數(shù),參數(shù)表示權(quán)限,則其權(quán)限可以訪問
  • hasIpAddress:如果有參數(shù),參數(shù)表示IP地址,如果用戶IP和參數(shù)匹配,則可以訪問
  • hasRole:如果有參數(shù),參數(shù)表示角色,則其角色可以訪問
  • permitAll:用戶可以任意訪問
  • rememberMe:允許通過remember-me登錄的用戶訪問
  • authenticated:用戶登錄后可訪問

認證失敗處理類

 
 
 
 
  1. /** 
  2.  *  返回未授權(quán) 
  3.  */ 
  4. @Component 
  5. public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { 
  6.  
  7.     private static final long serialVersionUID = -8970718410437077606L; 
  8.  
  9.     @Override 
  10.     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) 
  11.             throws IOException { 
  12.         int code = HttpStatus.UNAUTHORIZED; 
  13.         String msg = "認證失敗,無法訪問系統(tǒng)資源,請先登陸"; 
  14.         ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); 
  15.     } 

認證流程

自定義認證過濾器

 
 
 
 
  1. public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { 
  2.  
  3.     private AuthenticationManager authenticationManager; 
  4.  
  5.     private RsaKeyProperties prop; 
  6.  
  7.     public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) { 
  8.         this.authenticationManager = authenticationManager; 
  9.         this.prop = prop; 
  10.     } 
  11.  
  12.     /** 
  13.      * @author cheetah 
  14.      * @description 登陸驗證 
  15.      * @date 2021/6/28 16:17 
  16.      * @Param [request, response] 
  17.      * @return org.springframework.security.core.Authentication 
  18.      **/ 
  19.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 
  20.         try { 
  21.             UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class); 
  22.             UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()); 
  23.             return authenticationManager.authenticate(authRequest); 
  24.         }catch (Exception e){ 
  25.             try { 
  26.                 response.setContentType("application/json;charset=utf-8"); 
  27.                 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
  28.                 PrintWriter out = response.getWriter(); 
  29.                 Map resultMap = new HashMap(); 
  30.                 resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED); 
  31.                 resultMap.put("msg", "用戶名或密碼錯誤!"); 
  32.                 out.write(new ObjectMapper().writeValueAsString(resultMap)); 
  33.                 out.flush(); 
  34.                 out.close(); 
  35.             }catch (Exception outEx){ 
  36.                 outEx.printStackTrace(); 
  37.             } 
  38.             throw new RuntimeException(e); 
  39.         } 
  40.     } 
  41.  
  42.  
  43.     /** 
  44.      * @author cheetah 
  45.      * @description 登陸成功回調(diào) 
  46.      * @date 2021/6/28 16:17 
  47.      * @Param [request, response, chain, authResult] 
  48.      * @return void 
  49.      **/ 
  50.     public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 
  51.         UserPojo user = new UserPojo(); 
  52.         user.setUsername(authResult.getName()); 
  53.         user.setRoles((List)authResult.getAuthorities()); 
  54.         //通過私鑰進行加密:token有效期一天 
  55.         String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60); 
  56.         response.addHeader("Authorization", "Bearer "+token); 
  57.         try { 
  58.             response.setContentType("application/json;charset=utf-8"); 
  59.             response.setStatus(HttpServletResponse.SC_OK); 
  60.             PrintWriter out = response.getWriter(); 
  61.             Map resultMap = new HashMap(); 
  62.             resultMap.put("code", HttpServletResponse.SC_OK); 
  63.             resultMap.put("msg", "認證通過!"); 
  64.             resultMap.put("token", token); 
  65.             out.write(new ObjectMapper().writeValueAsString(resultMap)); 
  66.             out.flush(); 
  67.             out.close(); 
  68.         }catch (Exception outEx){ 
  69.             outEx.printStackTrace(); 
  70.         } 
  71.     } 

流程

Security默認登錄路徑為/login,當我們調(diào)用該接口時,它會調(diào)用上邊的attemptAuthentication方法;圖片圖片圖片圖片所以我們要自定義UserInfoService繼承UserDetailsService實現(xiàn)loadUserByUsername方法;

所以我們要自定義UserInfoService繼承UserDetailsService實現(xiàn)loadUserByUsername方法;

 
 
 
 
  1. public interface UserInfoService extends UserDetailsService { 
  2.  
  3.  
  4. @Service 
  5. @Transactional 
  6. public class UserInfoServiceImpl implements UserInfoService { 
  7.  
  8.     @Autowired 
  9.     private SysUserInfoMapper userInfoMapper; 
  10.  
  11.     @Override 
  12.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
  13.         UserPojo user = userInfoMapper.queryByUserName(username); 
  14.         return user; 
  15.     } 

其中的loadUserByUsername返回的是UserDetails類型,所以UserPojo繼承UserDetails類

 
 
 
 
  1. @Data 
  2. public class UserPojo implements UserDetails { 
  3.  
  4.     private Integer id; 
  5.  
  6.     private String username; 
  7.  
  8.     private String password; 
  9.  
  10.     private Integer status; 
  11.  
  12.     private List roles; 
  13.  
  14.     @JsonIgnore 
  15.     @Override 
  16.     public Collection getAuthorities() { 
  17.         //理想型返回 admin 權(quán)限,可自已處理這塊 
  18.         List auth = new ArrayList<>(); 
  19.         auth.add(new SimpleGrantedAuthority("ADMIN")); 
  20.         return auth; 
  21.     } 
  22.  
  23.     @Override 
  24.     public String getPassword() { 
  25.         return this.password; 
  26.     } 
  27.  
  28.     @Override 
  29.     public String getUsername() { 
  30.         return this.username; 
  31.     } 
  32.  
  33.     /** 
  34.      * 賬戶是否過期 
  35.      **/ 
  36.     @JsonIgnore 
  37.     @Override 
  38.     public boolean isAccountNonExpired() { 
  39.         return true; 
  40.     } 
  41.  
  42.     /** 
  43.      * 是否禁用 
  44.      */ 
  45.     @JsonIgnore 
  46.     @Override 
  47.     public boolean isAccountNonLocked() { 
  48.         return true; 
  49.     } 
  50.  
  51.     /** 
  52.      * 密碼是否過期 
  53.      */ 
  54.     @JsonIgnore 
  55.     @Override 
  56.     public boolean isCredentialsNonExpired() { 
  57.         return true; 
  58.     } 
  59.  
  60.     /** 
  61.      * 是否啟用 
  62.      */ 
  63.     @JsonIgnore 
  64.     @Override 
  65.     public boolean isEnabled() { 
  66.         return true; 
  67.     } 

當認證通過之后會在SecurityContext中設置Authentication對象,回調(diào)調(diào)用successfulAuthentication方法返回token信息,

整體流程圖如下

鑒權(quán)流程

自定義token過濾器

 
 
 
 
  1. public class TokenVerifyFilter extends BasicAuthenticationFilter { 
  2.     private RsaKeyProperties prop; 
  3.  
  4.     public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) { 
  5.         super(authenticationManager); 
  6.         this.prop = prop; 
  7.     } 
  8.  
  9.     public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 
  10.         String header = request.getHeader("Authorization"); 
  11.         if (header == null || !header.startsWith("Bearer ")) { 
  12.             //如果攜帶錯誤的token,則給用戶提示請登錄! 
  13.             chain.doFilter(request, response); 
  14.         } else { 
  15.             //如果攜帶了正確格式的token要先得到token 
  16.             String token = header.replace("Bearer ", ""); 
  17.             //通過公鑰進行解密:驗證tken是否正確 
  18.             Payload payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class); 
  19.             UserPojo user = payload.getUserInfo(); 
  20.             if(user!=null){ 
  21.                 UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); 
  22.                 //將認證信息存到安全上下文中 
  23.                 SecurityContextHolder.getContext().setAuthentication(authResult); 
  24.                 chain.doFilter(request, response); 
  25.             } 
  26.         } 
  27.     } 

當我們訪問時需要在header中攜帶token信息

本文轉(zhuǎn)載自微信公眾號「阿Q說代碼」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系阿Q說代碼公眾號。


分享名稱:實戰(zhàn)篇:Security+JWT組合拳
瀏覽地址:http://www.dlmjj.cn/article/dhsspcj.html