<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot 整合 Spring Security 實(shí)戰(zhàn)!

          共 46901字,需瀏覽 94分鐘

           ·

          2021-04-11 22:40

          作者:Sans_

          鏈接:juejin.cn/post/6844903974546456590

          一、說(shuō)明

          SpringSecurity是一個(gè)用于Java 企業(yè)級(jí)應(yīng)用程序的安全框架,主要包含用戶認(rèn)證和用戶授權(quán)兩個(gè)方面.相比較Shiro而言,Security功能更加的強(qiáng)大,它可以很容易地?cái)U(kuò)展以滿足更多安全控制方面的需求,但也相對(duì)它的學(xué)習(xí)成本會(huì)更高,兩種框架各有利弊.實(shí)際開(kāi)發(fā)中還是要根據(jù)業(yè)務(wù)和項(xiàng)目的需求來(lái)決定使用哪一種.

          JWT是在Web應(yīng)用中安全傳遞信息的規(guī)范,從本質(zhì)上來(lái)說(shuō)是Token的演變,是一種生成加密用戶身份信息的Token,特別適用于分布式單點(diǎn)登陸的場(chǎng)景,無(wú)需在服務(wù)端保存用戶的認(rèn)證信息,而是直接對(duì)Token進(jìn)行校驗(yàn)獲取用戶信息,使單點(diǎn)登錄更為簡(jiǎn)單靈活.

          更多 Spring Boot 實(shí)戰(zhàn)可以關(guān)注公眾號(hào)「Java后端」,回復(fù) 666 即可。

          二、項(xiàng)目環(huán)境

          SpringBoot版本:2.1.6

          SpringSecurity版本: 5.1.5

          MyBatis-Plus版本: 3.1.0

          JDK版本:1.8

          數(shù)據(jù)表(SQL文件在項(xiàng)目中): 數(shù)據(jù)庫(kù)中測(cè)試號(hào)的密碼進(jìn)行了加密,密碼皆為123456:

          Maven依賴如下:

          <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>mysql</groupId>
                      <artifactId>mysql-connector-java</artifactId>
                      <scope>runtime</scope>
                  </dependency>
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                      <optional>true</optional>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
                  <!--Security依賴 -->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                  </dependency>
                  <!-- MybatisPlus 核心庫(kù) -->
                  <dependency>
                      <groupId>com.baomidou</groupId>
                      <artifactId>mybatis-plus-boot-starter</artifactId>
                      <version>3.1.0</version>
                  </dependency>
                  <!-- 引入阿里數(shù)據(jù)庫(kù)連接池 -->
                  <dependency>
                      <groupId>com.alibaba</groupId>
                      <artifactId>druid</artifactId>
                      <version>1.1.6</version>
                  </dependency>
                  <!-- StringUtilS工具 -->
                  <dependency>
                      <groupId>org.apache.commons</groupId>
                      <artifactId>commons-lang3</artifactId>
                      <version>3.5</version>
                  </dependency>
                  <!-- JSON工具 -->
                  <dependency>
                      <groupId>com.alibaba</groupId>
                      <artifactId>fastjson</artifactId>
                      <version>1.2.45</version>
                  </dependency>
                  <!-- JWT依賴 -->
                  <dependency>
                      <groupId>org.springframework.security</groupId>
                      <artifactId>spring-security-jwt</artifactId>
                      <version>1.0.9.RELEASE</version>
                  </dependency>
                  <dependency>
                      <groupId>io.jsonwebtoken</groupId>
                      <artifactId>jjwt</artifactId>
                      <version>0.9.0</version>
                  </dependency>
          </dependencies>

          配置如下:

          # 配置端口
          server:
            port: 8764
          spring:
            # 配置數(shù)據(jù)源
            datasource:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/sans_security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
              username: root
              password: 123456
              type: com.alibaba.druid.pool.DruidDataSource
          # JWT配置
          jwt:
            # 密匙KEY
            secret: JWTSecret
            # HeaderKEY
            tokenHeader: Authorization
            # Token前綴字符
            tokenPrefix: Sans-
            # 過(guò)期時(shí)間 單位秒 1天后過(guò)期=86400 7天后過(guò)期=604800
            expiration: 86400
            # 配置不需要認(rèn)證的接口
            antMatchers: /index/**,/login/**,/favicon.ico
          # Mybatis-plus相關(guān)配置
          mybatis-plus:
            # xml掃描,多個(gè)目錄用逗號(hào)或者分號(hào)分隔(告訴 Mapper 所對(duì)應(yīng)的 XML 文件位置)
            mapper-locations: classpath:mapper/*.xml
            # 以下配置均有默認(rèn)值,可以不設(shè)置
            global-config:
              db-config:
                #主鍵類(lèi)型 AUTO:"數(shù)據(jù)庫(kù)ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數(shù)字類(lèi)型唯一ID)", UUID:"全局唯一ID UUID";
                id-type: AUTO
                #字段策略 IGNORED:"忽略判斷" NOT_NULL:"非 NULL 判斷") NOT_EMPTY:"非空判斷"
                field-strategy: NOT_EMPTY
                #數(shù)據(jù)庫(kù)類(lèi)型
                db-type: MYSQL
            configuration:
              # 是否開(kāi)啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫(kù)列名到Java屬性駝峰命名的類(lèi)似映射
              map-underscore-to-camel-case: true
              # 返回map時(shí)true:當(dāng)查詢數(shù)據(jù)為空時(shí)字段返回為null,false:不加這個(gè)查詢數(shù)據(jù)為空時(shí),字段將被隱藏
              call-setters-on-nulls: true
              # 這個(gè)配置會(huì)將執(zhí)行的sql打印出來(lái),在開(kāi)發(fā)或測(cè)試的時(shí)候可以用
              log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

          三、編寫(xiě)項(xiàng)目基礎(chǔ)類(lèi)

          Entity,Dao,Service,及等SpringSecurity用戶的Entity,Service類(lèi)等在這里省略,請(qǐng)參考源碼

          編寫(xiě)JWT工具類(lèi)

          /**
           * JWT工具類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/2 7:42
           */

          @Slf4j
          public class JWTTokenUtil {

              /**
               * 生成Token
               * @Author Sans
               * @CreateTime 2019/10/2 12:16
               * @Param  selfUserEntity 用戶安全實(shí)體
               * @Return Token
               */

              public static String createAccessToken(SelfUserEntity selfUserEntity){
                  // 登陸成功生成JWT
                  String token = Jwts.builder()
                          // 放入用戶名和用戶ID
                          .setId(selfUserEntity.getUserId()+"")
                          // 主題
                          .setSubject(selfUserEntity.getUsername())
                          // 簽發(fā)時(shí)間
                          .setIssuedAt(new Date())
                          // 簽發(fā)者
                          .setIssuer("sans")
                          // 自定義屬性 放入用戶擁有權(quán)限
                          .claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities()))
                          // 失效時(shí)間
                          .setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration))
                          // 簽名算法和密鑰
                          .signWith(SignatureAlgorithm.HS512, JWTConfig.secret)
                          .compact();
                  return token;
              }
          }

          編寫(xiě)暫無(wú)權(quán)限處理類(lèi)

          /**
           * @Description 暫無(wú)權(quán)限處理類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/3 8:39
           */

          @Component
          public class UserAuthAccessDeniedHandler implements AccessDeniedHandler{
              /**
               * 暫無(wú)權(quán)限返回結(jié)果
               * @Author Sans
               * @CreateTime 2019/10/3 8:41
               */

              @Override
              public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
                  ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授權(quán)"));
              }
          }

          編寫(xiě)登錄失敗處理類(lèi)

          /**
           * @Description 登錄失敗處理類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/3 9:06
           */

          @Slf4j
          @Component
          public class UserLoginFailureHandler implements AuthenticationFailureHandler {
              /**
               * 登錄失敗返回結(jié)果
               * @Author Sans
               * @CreateTime 2019/10/3 9:12
               */

              @Override
              public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
                  // 這些對(duì)于操作的處理類(lèi)可以根據(jù)不同異常進(jìn)行不同處理
                  if (exception instanceof UsernameNotFoundException){
                      log.info("【登錄失敗】"+exception.getMessage());
                      ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名不存在"));
                  }
                  if (exception instanceof LockedException){
                      log.info("【登錄失敗】"+exception.getMessage());
                      ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶被凍結(jié)"));
                  }
                  if (exception instanceof BadCredentialsException){
                      log.info("【登錄失敗】"+exception.getMessage());
                      ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名密碼不正確"));
                  }
                  ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登錄失敗"));
              }
          }

          編寫(xiě)登錄成功處理類(lèi)

          /**
           * @Description 登錄成功處理類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/3 9:13
           */

          @Slf4j
          @Component
          public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
              /**
               * 登錄成功返回結(jié)果
               * @Author Sans
               * @CreateTime 2019/10/3 9:27
               */

              @Override
              public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
                  // 組裝JWT
                  SelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal();
                  String token = JWTTokenUtil.createAccessToken(selfUserEntity);
                  token = JWTConfig.tokenPrefix + token;
                  // 封裝返回參數(shù)
                  Map<String,Object> resultData = new HashMap<>();
                  resultData.put("code","200");
                  resultData.put("msg", "登錄成功");
                  resultData.put("token",token);
                  ResultUtil.responseJson(response,resultData);
              }
          }

          編寫(xiě)登出成功處理類(lèi)

          /**
           * 用戶登出類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/3 9:42
           */

          @Component
          public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
              /**
               * 用戶登出返回結(jié)果
               * 這里應(yīng)該讓前端清除掉Token
               * @Author Sans
               * @CreateTime 2019/10/3 9:50
               */

              @Override
              public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
                  Map<String,Object> resultData = new HashMap<>();
                  resultData.put("code","200");
                  resultData.put("msg", "登出成功");
                  SecurityContextHolder.clearContext();
                  ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));
              }
          }

          四、編寫(xiě)Security核心類(lèi)

          編寫(xiě)自定義登錄驗(yàn)證類(lèi)

          /**
           * 自定義登錄驗(yàn)證
           * @Author Sans
           * @CreateTime 2019/10/1 19:11
           */

          @Component
          public class UserAuthenticationProvider implements AuthenticationProvider {
              @Autowired
              private SelfUserDetailsService selfUserDetailsService;
              @Autowired
              private SysUserService sysUserService;
              @Override
              public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                  // 獲取表單輸入中返回的用戶名
                  String userName = (String) authentication.getPrincipal();
                  // 獲取表單中輸入的密碼
                  String password = (String) authentication.getCredentials();
                  // 查詢用戶是否存在
                  SelfUserEntity userInfo = selfUserDetailsService.loadUserByUsername(userName);
                  if (userInfo == null) {
                      throw new UsernameNotFoundException("用戶名不存在");
                  }
                  // 我們還要判斷密碼是否正確,這里我們的密碼使用BCryptPasswordEncoder進(jìn)行加密的
                  if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
                      throw new BadCredentialsException("密碼不正確");
                  }
                  // 還可以加一些其他信息的判斷,比如用戶賬號(hào)已停用等判斷
                  if (userInfo.getStatus().equals("PROHIBIT")){
                      throw new LockedException("該用戶已被凍結(jié)");
                  }
                  // 角色集合
                  Set<GrantedAuthority> authorities = new HashSet<>();
                  // 查詢用戶角色
                  List<SysRoleEntity> sysRoleEntityList = sysUserService.selectSysRoleByUserId(userInfo.getUserId());
                  for (SysRoleEntity sysRoleEntity: sysRoleEntityList){
                      authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getRoleName()));
                  }
                  userInfo.setAuthorities(authorities);
                  // 進(jìn)行登錄
                  return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
              }
              @Override
              public boolean supports(Class<?> authentication) {
                  return true;
              }
          }

          編寫(xiě)自定義PermissionEvaluator注解驗(yàn)證

          /**
           * 自定義權(quán)限注解驗(yàn)證
           * @Author Sans
           * @CreateTime 2019/10/6 13:31
           */

          @Component
          public class UserPermissionEvaluator implements PermissionEvaluator {
              @Autowired
              private SysUserService sysUserService;
              /**
               * hasPermission鑒權(quán)方法
               * 這里僅僅判斷PreAuthorize注解中的權(quán)限表達(dá)式
               * 實(shí)際中可以根據(jù)業(yè)務(wù)需求設(shè)計(jì)數(shù)據(jù)庫(kù)通過(guò)targetUrl和permission做更復(fù)雜鑒權(quán)
               * @Author Sans
               * @CreateTime 2019/10/6 18:25
               * @Param  authentication 用戶身份
               * @Param  targetUrl 請(qǐng)求路徑
               * @Param  permission 請(qǐng)求路徑權(quán)限
               * @Return boolean 是否通過(guò)
               */

              @Override
              public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
                  // 獲取用戶信息
                  SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();
                  // 查詢用戶權(quán)限(這里可以將權(quán)限放入緩存中提升效率)
                  Set<String> permissions = new HashSet<>();
                  List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
                  for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {
                      permissions.add(sysMenuEntity.getPermission());
                  }
                  // 權(quán)限對(duì)比
                  if (permissions.contains(permission.toString())){
                      return true;
                  }
                  return false;
              }
              @Override
              public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
                  return false;
              }
          }

          編寫(xiě)SpringSecurity核心配置類(lèi)

          /**
           * SpringSecurity核心配置類(lèi)
           * @Author Sans
           * @CreateTime 2019/10/1 9:40
           */

          @Configuration
          @EnableWebSecurity
          @EnableGlobalMethodSecurity(prePostEnabled = true) //開(kāi)啟權(quán)限注解,默認(rèn)是關(guān)閉的
          public class SecurityConfig extends WebSecurityConfigurerAdapter {
              /**
               * 自定義登錄成功處理器
               */

              @Autowired
              private UserLoginSuccessHandler userLoginSuccessHandler;
              /**
               * 自定義登錄失敗處理器
               */

              @Autowired
              private UserLoginFailureHandler userLoginFailureHandler;
              /**
               * 自定義注銷(xiāo)成功處理器
               */

              @Autowired
              private UserLogoutSuccessHandler userLogoutSuccessHandler;
              /**
               * 自定義暫無(wú)權(quán)限處理器
               */

              @Autowired
              private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
              /**
               * 自定義未登錄的處理器
               */

              @Autowired
              private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
              /**
               * 自定義登錄邏輯驗(yàn)證器
               */

              @Autowired
              private UserAuthenticationProvider userAuthenticationProvider;
              
              /**
               * 加密方式
               * @Author Sans
               * @CreateTime 2019/10/1 14:00
               */

              @Bean
              public BCryptPasswordEncoder bCryptPasswordEncoder(){
                  return new BCryptPasswordEncoder();
              }
              /**
               * 注入自定義PermissionEvaluator
               */

              @Bean
              public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
                  DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
                  handler.setPermissionEvaluator(new UserPermissionEvaluator());
                  return handler;
              }
              
              /**
               * 配置登錄驗(yàn)證邏輯
               */

              @Override
              protected void configure(AuthenticationManagerBuilder auth){
                  //這里可啟用我們自己的登陸驗(yàn)證邏輯
                  auth.authenticationProvider(userAuthenticationProvider);
              }
              /**
               * 配置security的控制邏輯
               * @Author Sans
               * @CreateTime 2019/10/1 16:56
               * @Param  http 請(qǐng)求
               */

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.authorizeRequests()
                          //不進(jìn)行權(quán)限驗(yàn)證的請(qǐng)求或資源(從配置文件中讀取)
                         .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
                          //其他的需要登陸后才能訪問(wèn)
                          .anyRequest().authenticated()
                          .and()
                          //配置未登錄自定義處理類(lèi)
                          .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                          .and()
                          //配置登錄地址
                          .formLogin()
                          .loginProcessingUrl("/login/userLogin")
                          //配置登錄成功自定義處理類(lèi)
                          .successHandler(userLoginSuccessHandler)
                          //配置登錄失敗自定義處理類(lèi)
                          .failureHandler(userLoginFailureHandler)
                          .and()
                          //配置登出地址
                          .logout()
                          .logoutUrl("/login/userLogout")
                          //配置用戶登出自定義處理類(lèi)
                          .logoutSuccessHandler(userLogoutSuccessHandler)
                          .and()
                          //配置沒(méi)有權(quán)限自定義處理類(lèi)
                          .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                          .and()
                          // 開(kāi)啟跨域
                          .cors()
                          .and()
                          // 取消跨站請(qǐng)求偽造防護(hù)
                          .csrf().disable();
                  // 基于Token不需要session
                  http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                  // 禁用緩存
                  http.headers().cacheControl();
                  // 添加JWT過(guò)濾器
                  http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
              }
          }

          五、編寫(xiě)JWT攔截類(lèi)

          編寫(xiě)JWT接口請(qǐng)求校驗(yàn)攔截器

          /**
           * JWT接口請(qǐng)求校驗(yàn)攔截器
           * 請(qǐng)求接口時(shí)會(huì)進(jìn)入這里驗(yàn)證Token是否合法和過(guò)期
           * @Author Sans
           * @CreateTime 2019/10/5 16:41
           */

          @Slf4j
          public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
              public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
                  super(authenticationManager);
              }
              @Override
              protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                  // 獲取請(qǐng)求頭中JWT的Token
                  String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
                  if (null!=tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
                      try {
                          // 截取JWT前綴
                          String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
                          // 解析JWT
                          Claims claims = Jwts.parser()
                                  .setSigningKey(JWTConfig.secret)
                                  .parseClaimsJws(token)
                                  .getBody();
                          // 獲取用戶名
                          String username = claims.getSubject();
                          String userId=claims.getId();
                          if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(userId)) {
                              // 獲取角色
                              List<GrantedAuthority> authorities = new ArrayList<>();
                              String authority = claims.get("authorities").toString();
                              if(!StringUtils.isEmpty(authority)){
                                  List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
                                  for(Map<String,String> role : authorityMap){
                                      if(!StringUtils.isEmpty(role)) {
                                          authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                                      }
                                  }
                              }
                              //組裝參數(shù)
                              SelfUserEntity selfUserEntity = new SelfUserEntity();
                              selfUserEntity.setUsername(claims.getSubject());
                              selfUserEntity.setUserId(Long.parseLong(claims.getId()));
                              selfUserEntity.setAuthorities(authorities);
                              UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
                              SecurityContextHolder.getContext().setAuthentication(authentication);
                          }
                      } catch (ExpiredJwtException e){
                          log.info("Token過(guò)期");
                      } catch (Exception e) {
                          log.info("Token無(wú)效");
                      }
                  }
                  filterChain.doFilter(request, response);
                  return;
              }
          }

          六、權(quán)限注解和hasPermission權(quán)限擴(kuò)展

          Security允許我們?cè)诙xURL方法訪問(wèn)所應(yīng)有的注解權(quán)限時(shí)使用SpringEL表達(dá)式,在定義所需的訪問(wèn)權(quán)限時(shí)如果對(duì)應(yīng)的表達(dá)式返回結(jié)果為true則表示擁有對(duì)應(yīng)的權(quán)限,反之則沒(méi)有權(quán)限,會(huì)進(jìn)入到我們配置的UserAuthAccessDeniedHandler(暫無(wú)權(quán)限處理類(lèi))中進(jìn)行處理.這里舉一些例子,代碼中注釋有對(duì)應(yīng)的描述。

          表達(dá)式

          /**
               * 管理端信息
               * @Author Sans
               * @CreateTime 2019/10/2 14:22
               * @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
               */

              @PreAuthorize("hasRole('ADMIN')")
              @RequestMapping(value = "/info",method = RequestMethod.GET)
              public Map<String,Object> userLogin(){
                  Map<String,Object> result = new HashMap<>();
                  SelfUserEntity userDetails = SecurityUtil.getUserInfo();
                  result.put("title","管理端信息");
                  result.put("data",userDetails);
                  return ResultUtil.resultSuccess(result);
              }
              /**
               * 擁有ADMIN或者USER角色可以訪問(wèn)
               * @Author Sans
               * @CreateTime 2019/10/2 14:22
               * @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
               */

              @PreAuthorize("hasAnyRole('ADMIN','USER')")
              @RequestMapping(value = "/list",method = RequestMethod.GET)
              public Map<String,Object> list(){
                  Map<String,Object> result = new HashMap<>();
                  List<SysUserEntity> sysUserEntityList = sysUserService.list();
                  result.put("title","擁有用戶或者管理員角色都可以查看");
                  result.put("data",sysUserEntityList);
                  return ResultUtil.resultSuccess(result);
              }
              /**
               * 擁有ADMIN和USER角色可以訪問(wèn)
               * @Author Sans
               * @CreateTime 2019/10/2 14:22
               * @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
               */

              @PreAuthorize("hasRole('ADMIN') and hasRole('USER')")
              @RequestMapping(value = "/menuList",method = RequestMethod.GET)
              public Map<String,Object> menuList(){
                  Map<String,Object> result = new HashMap<>();
                  List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
                  result.put("title","擁有用戶和管理員角色都可以查看");
                  result.put("data",sysMenuEntityList);
                  return ResultUtil.resultSuccess(result);
              }

          通常情況下使用hasRole和hasAnyRole基本可以滿足大部分鑒權(quán)需求,但是有時(shí)候面對(duì)更復(fù)雜的場(chǎng)景上述常規(guī)表示式無(wú)法完成權(quán)限認(rèn)證,Security也為我們提供了解決方案.通過(guò)hasPermission()來(lái)擴(kuò)展表達(dá)式.使用hasPermission()首先要實(shí)現(xiàn)PermissionEvaluator接口

          /**
           * 自定義權(quán)限注解驗(yàn)證
           * @Author Sans
           * @CreateTime 2019/10/6 13:31
           */

          @Component
          public class UserPermissionEvaluator implements PermissionEvaluator {
              @Autowired
              private SysUserService sysUserService;
              /**
               * hasPermission鑒權(quán)方法
               * 這里僅僅判斷PreAuthorize注解中的權(quán)限表達(dá)式
               * 實(shí)際中可以根據(jù)業(yè)務(wù)需求設(shè)計(jì)數(shù)據(jù)庫(kù)通過(guò)targetUrl和permission做更復(fù)雜鑒權(quán)
               * 當(dāng)然targetUrl不一定是URL可以是數(shù)據(jù)Id還可以是管理員標(biāo)識(shí)等,這里根據(jù)需求自行設(shè)計(jì)
               * @Author Sans
               * @CreateTime 2019/10/6 18:25
               * @Param  authentication 用戶身份(在使用hasPermission表達(dá)式時(shí)Authentication參數(shù)默認(rèn)會(huì)自動(dòng)帶上)
               * @Param  targetUrl 請(qǐng)求路徑
               * @Param  permission 請(qǐng)求路徑權(quán)限
               * @Return boolean 是否通過(guò)
               */

              @Override
              public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
                  // 獲取用戶信息
                  SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();
                  // 查詢用戶權(quán)限(這里可以將權(quán)限放入緩存中提升效率)
                  Set<String> permissions = new HashSet<>();
                  List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
                  for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {
                      permissions.add(sysMenuEntity.getPermission());
                  }
                  // 權(quán)限對(duì)比
                  if (permissions.contains(permission.toString())){
                      return true;
                  }
                  return false;
              }
              @Override
              public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
                  return false;
              }
          }

          在請(qǐng)求方法上添加hasPermission示例

          /**
               * 擁有sys:user:info權(quán)限可以訪問(wèn)
               * hasPermission 第一個(gè)參數(shù)是請(qǐng)求路徑 第二個(gè)參數(shù)是權(quán)限表達(dá)式
               * @Author Sans
               * @CreateTime 2019/10/2 14:22
               * @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
               */

              @PreAuthorize("hasPermission('/admin/userList','sys:user:info')")
              @RequestMapping(value = "/userList",method = RequestMethod.GET)
              public Map<String,Object> userList(){
                  Map<String,Object> result = new HashMap<>();
                  List<SysUserEntity> sysUserEntityList = sysUserService.list();
                  result.put("title","擁有sys:user:info權(quán)限都可以查看");
                  result.put("data",sysUserEntityList);
                  return ResultUtil.resultSuccess(result);
              }

          hasPermission可以也可以和其他表達(dá)式聯(lián)合使用

          /**
               * 擁有ADMIN角色和sys:role:info權(quán)限可以訪問(wèn)
               * @Author Sans
               * @CreateTime 2019/10/2 14:22
               * @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
               */

              @PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")
              @RequestMapping(value = "/adminRoleList",method = RequestMethod.GET)
              public Map<String,Object> adminRoleList(){
                  Map<String,Object> result = new HashMap<>();
                  List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
                  result.put("title","擁有ADMIN角色和sys:role:info權(quán)限可以訪問(wèn)");
                  result.put("data",sysRoleEntityList);
                  return ResultUtil.resultSuccess(result);
              }

          七、測(cè)試

          創(chuàng)建賬戶這里用戶加密使用了Security推薦的bCryptPasswordEncoder方法

          /**
               * 注冊(cè)用戶
               */

              @Test
              public void contextLoads() 
          {
                  // 注冊(cè)用戶
                  SysUserEntity sysUserEntity = new SysUserEntity();
                  sysUserEntity.setUsername("sans");
                  sysUserEntity.setPassword(bCryptPasswordEncoder.encode("123456"));
                  // 設(shè)置用戶狀態(tài)
                  sysUserEntity.setStatus("NORMAL");
                  sysUserService.save(sysUserEntity);
                  // 分配角色 1:ADMIN 2:USER
                  SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();
                  sysUserRoleEntity.setRoleId(2L);
                  sysUserRoleEntity.setUserId(sysUserEntity.getUserId());
                  sysUserRoleService.save(sysUserRoleEntity);
              }

          登錄USER角色賬號(hào),登錄成功后我們會(huì)獲取到身份認(rèn)證的Token

          訪問(wèn)USER角色的接口,把上一步獲取到的Token設(shè)置在Headers中,Key為Authorization,我們之前實(shí)現(xiàn)的JWTAuthenticationTokenFilter攔截器會(huì)根據(jù)請(qǐng)求頭中的Authorization獲取并解析Token

          使用USER角色Token訪問(wèn)ADMIN角色的接口,會(huì)被拒絕,告知未授權(quán)(暫無(wú)權(quán)限會(huì)進(jìn)入我們定義的UserAuthAccessDeniedHandler這個(gè)類(lèi)進(jìn)行處理)

          更換ADMIN角色進(jìn)行登錄并訪問(wèn)ADMIN接口

          八.項(xiàng)目源碼

          碼云:gitee.com/liselotte/spring-boot-security-demo

          GitHub:github.com/xuyulong2017/my-java-demo


          點(diǎn)擊閱讀全文前往微服務(wù)電商教程
          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码人妻一区二区蜜桃视频 | 精品久久视频 | 大鸡吧av网站 | 性爱国产一区 | 日屄在线免费视频 |