1. 程式人生 > 程式設計 >SpringBoot 整合SpringSecurity示例實現前後分離許可權註解+JWT登入認證

SpringBoot 整合SpringSecurity示例實現前後分離許可權註解+JWT登入認證

一.說明

SpringSecurity是一個用於Java 企業級應用程式的安全框架,主要包含使用者認證和使用者授權兩個方面.相比較Shiro而言,Security功能更加的強大,它可以很容易地擴充套件以滿足更多安全控制方面的需求,但也相對它的學習成本會更高,兩種框架各有利弊.實際開發中還是要根據業務和專案的需求來決定使用哪一種.

JWT是在Web應用中安全傳遞資訊的規範,從本質上來說是Token的演變,是一種生成加密使用者身份資訊的Token,特別適用於分散式單點登陸的場景,無需在服務端儲存使用者的認證資訊,而是直接對Token進行校驗獲取使用者資訊,使單點登入更為簡單靈活.

二.專案環境

SpringBoot版本:2.1.6

SpringSecurity版本: 5.1.5

MyBatis-Plus版本: 3.1.0

JDK版本:1.8

資料表(SQL檔案在專案中):資料庫中測試號的密碼進行了加密,密碼皆為123456

資料表名 中文表名 備註說明
sys_user 系統使用者表 基礎表
sys_menu 許可權表 基礎表
sys_role 角色表 基礎表
sys_role_menu 角色與許可權關係表 中間表
sys_user_role 使用者與角色關係表 中間表

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 核心庫 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <!-- 引入阿里資料庫連線池 --> <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:
  # 配置資料來源
  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-
  # 過期時間 單位秒 1天后過期=86400 7天后過期=604800
  expiration: 86400
  # 配置不需要認證的介面
  antMatchers: /index,/login/**,/favicon.ico
# Mybatis-plus相關配置
mybatis-plus:
  # xml掃描,多個目錄用逗號或者分號分隔(告訴 Mapper 所對應的 XML 檔案位置)
  mapper-locations: classpath:mapper/*.xml
  # 以下配置均有預設值,可以不設定
  global-config:
    db-config:
      #主鍵型別 AUTO:"資料庫ID自增" INPUT:"使用者輸入ID",ID_WORKER:"全域性唯一ID (數字型別唯一ID)",UUID:"全域性唯一ID UUID";
      id-type: AUTO
      #欄位策略 IGNORED:"忽略判斷"  NOT_NULL:"非 NULL 判斷")  NOT_EMPTY:"非空判斷"
      field-strategy: NOT_EMPTY
      #資料庫型別
      db-type: MYSQL
  configuration:
    # 是否開啟自動駝峰命名規則對映:從資料庫列名到Java屬性駝峰命名的類似對映
    map-underscore-to-camel-case: true
    # 返回map時true:當查詢資料為空時欄位返回為null,false:不加這個查詢資料為空時,欄位將被隱藏
    call-setters-on-nulls: true
    # 這個配置會將執行的sql打印出來,在開發或測試的時候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
複製程式碼

三.編寫專案基礎類

Entity,Dao,Service,及等SpringSecurity使用者的Entity,Service類等在這裡省略,請參考原始碼

編寫JWT工具類

/**
 * JWT工具類
 * @Author Sans
 * @CreateTime 2019/10/2 7:42
 */
@Slf4j
public class JWTTokenUtil {

    /**
     * 生成Token
     * @Author Sans
     * @CreateTime 2019/10/2 12:16
     * @Param  selfUserEntity 使用者安全實體
     * @Return Token
     */
    public static String createAccessToken(SelfUserEntity selfUserEntity){
        // 登陸成功生成JWT
        String token = Jwts.builder()
                // 放入使用者名稱和使用者ID
                .setId(selfUserEntity.getUserId()+"")
                // 主題
                .setSubject(selfUserEntity.getUsername())
                // 簽發時間
                .setIssuedAt(new Date())
                // 簽發者
                .setIssuer("sans")
                // 自定義屬性 放入使用者擁有許可權
                .claim("authorities",JSON.toJSONString(selfUserEntity.getAuthorities()))
                // 失效時間
                .setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration))
                // 簽名演演算法和金鑰
                .signWith(SignatureAlgorithm.HS512,JWTConfig.secret)
                .compact();
        return token;
    }
}
複製程式碼

編寫暫無許可權處理類

/**
 * @Description 暫無許可權處理類
 * @Author Sans
 * @CreateTime 2019/10/3 8:39
 */
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler{
    /**
     * 暫無許可權返回結果
     * @Author Sans
     * @CreateTime 2019/10/3 8:41
     */
    @Override
    public void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授權"));
    }
}
複製程式碼

編寫使用者未登入處理類

/**
 * 使用者未登入處理類
 * @Author Sans
 * @CreateTime 2019/10/3 8:55
 */
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
    /**
     * 使用者未登入返回結果
     * @Author Sans
     * @CreateTime 2019/10/3 9:01
     */
    @Override
    public void commence(HttpServletRequest request,AuthenticationException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登入"));
    }
}
複製程式碼

編寫登入失敗處理類

/**
 * @Description 登入失敗處理類
 * @Author Sans
 * @CreateTime 2019/10/3 9:06
 */
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    /**
     * 登入失敗返回結果
     * @Author Sans
     * @CreateTime 2019/10/3 9:12
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,AuthenticationException exception){
        // 這些對於操作的處理類可以根據不同異常進行不同處理
        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,"使用者被凍結"));
        }
        if (exception instanceof BadCredentialsException){
            log.info("【登入失敗】"+exception.getMessage());
            ResultUtil.responseJson(response,"使用者名稱密碼不正確"));
        }
        ResultUtil.responseJson(response,"登入失敗"));
    }
}
複製程式碼

編寫登入成功處理類

/**
 * @Description 登入成功處理類
 * @Author Sans
 * @CreateTime 2019/10/3 9:13
 */
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    /**
     * 登入成功返回結果
     * @Author Sans
     * @CreateTime 2019/10/3 9:27
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,Authentication authentication){
        // 組裝JWT
        SelfUserEntity selfUserEntity =  (SelfUserEntity) authentication.getPrincipal();
        String token = JWTTokenUtil.createAccessToken(selfUserEntity);
        token = JWTConfig.tokenPrefix + token;
        // 封裝返回引數
        Map<String,Object> resultData = new HashMap<>();
        resultData.put("code","200");
        resultData.put("msg","登入成功");
        resultData.put("token",token);
        ResultUtil.responseJson(response,resultData);
    }
}
複製程式碼

編寫登出成功處理類

/**
 * 使用者登出類
 * @Author Sans
 * @CreateTime 2019/10/3 9:42
 */
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
    /**
     * 使用者登出返回結果
     * 這裡應該讓前端清除掉Token
     * @Author Sans
     * @CreateTime 2019/10/3 9:50
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request,Authentication authentication){
        Map<String,"登出成功");
        SecurityContextHolder.clearContext();
        ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));
    }
}
複製程式碼

四.編寫Security核心類

編寫自定義登入驗證類

/**
 * 自定義登入驗證
 * @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進行加密的
        if (!new BCryptPasswordEncoder().matches(password,userInfo.getPassword())) {
            throw new BadCredentialsException("密碼不正確");
        }
        // 還可以加一些其他資訊的判斷,比如使用者賬號已停用等判斷
        if (userInfo.getStatus().equals("PROHIBIT")){
            throw new LockedException("該使用者已被凍結");
        }
        // 角色集合
        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);
        // 進行登入
        return new UsernamePasswordAuthenticationToken(userInfo,password,authorities);
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}
複製程式碼

編寫自定義PermissionEvaluator註解驗證

/**
 * 自定義許可權註解驗證
 * @Author Sans
 * @CreateTime 2019/10/6 13:31
 */
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private SysUserService sysUserService;
    /**
     * hasPermission鑑權方法
     * 這裡僅僅判斷PreAuthorize註解中的許可權表示式
     * 實際中可以根據業務需求設計資料庫通過targetUrl和permission做更復雜鑑權
     * @Author Sans
     * @CreateTime 2019/10/6 18:25
     * @Param  authentication  使用者身份
     * @Param  targetUrl  請求路徑
     * @Param  permission 請求路徑許可權
     * @Return boolean 是否通過
     */
    @Override
    public boolean hasPermission(Authentication authentication,Object targetUrl,Object permission) {
        // 獲取使用者資訊
        SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();
        // 查詢使用者許可權(這裡可以將許可權放入快取中提升效率)
        Set<String> permissions = new HashSet<>();
        List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
        for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {
            permissions.add(sysMenuEntity.getPermission());
        }
        // 許可權對比
        if (permissions.contains(permission.toString())){
            return true;
        }
        return false;
    }
    @Override
    public boolean hasPermission(Authentication authentication,Serializable targetId,String targetType,Object permission) {
        return false;
    }
}
複製程式碼

編寫SpringSecurity核心配置類

/**
 * SpringSecurity核心配置類
 * @Author Sans
 * @CreateTime 2019/10/1 9:40
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟許可權註解,預設是關閉的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定義登入成功處理器
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;
    /**
     * 自定義登入失敗處理器
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;
    /**
     * 自定義登出成功處理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;
    /**
     * 自定義暫無許可權處理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
    /**
     * 自定義未登入的處理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
    /**
     * 自定義登入邏輯驗證器
     */
    @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;
    }
    
    /**
     * 配置登入驗證邏輯
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        //這裡可啟用我們自己的登陸驗證邏輯
        auth.authenticationProvider(userAuthenticationProvider);
    }
    /**
     * 配置security的控制邏輯
     * @Author Sans
     * @CreateTime 2019/10/1 16:56
     * @Param  http 請求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //不進行許可權驗證的請求或資源(從配置檔案中讀取)
               .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
                //其他的需要登陸後才能訪問
                .anyRequest().authenticated()
                .and()
                //配置未登入自定義處理類
                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                //配置登入地址
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                //配置登入成功自定義處理類
                .successHandler(userLoginSuccessHandler)
                //配置登入失敗自定義處理類
                .failureHandler(userLoginFailureHandler)
                .and()
                //配置登出地址
                .logout()
                .logoutUrl("/login/userLogout")
                //配置使用者登出自定義處理類
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                //配置沒有許可權自定義處理類
                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 取消跨站請求偽造防護
                .csrf().disable();
        // 基於Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用快取
        http.headers().cacheControl();
        // 新增JWT過濾器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
    }
}
複製程式碼

五.編寫JWT攔截類

編寫JWT介面請求校驗攔截器

/**
 * JWT介面請求校驗攔截器
 * 請求介面時會進入這裡驗證Token是否合法和過期
 * @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,FilterChain filterChain) throws ServletException,IOException {
        // 獲取請求頭中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")));
                            }
                        }
                    }
                    //組裝引數
                    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過期");
            } catch (Exception e) {
                log.info("Token無效");
            }
        }
        filterChain.doFilter(request,response);
        return;
    }
}
複製程式碼

六.許可權註解和hasPermission許可權擴充套件

Security允許我們在定義URL方法訪問所應有的註解許可權時使用SpringEL表示式,在定義所需的訪問許可權時如果對應的表示式返回結果為true則表示擁有對應的許可權,反之則沒有許可權,會進入到我們配置的UserAuthAccessDeniedHandler(暫無許可權處理類)中進行處理.這裡舉一些例子,程式碼中註釋有對應的描述.

表示式 描述
hasRole([role]) 當前使用者是否擁有指定角色.
hasAnyRole([role1,role2]) 多個角色是一個以逗號進行分隔的字串如果當前使用者擁有指定角色中的任意一個則返回true.
hasAuthority([auth]) 等同於hasRole
hasAnyAuthority([auth1,auth2]) 等同於hasAnyRole
Principle 代表當前使用者的principle物件
authentication 直接從SecurityContext獲取的當前Authentication物件
permitAll 總是返回true表示允許所有的
denyAll 總是返回false表示拒絕所有的
isAnonymous() 當前使用者是否是一個匿名使用者
isRememberMe() 表示當前使用者是否是通過Remember-Me自動登入的
isAuthenticated() 表示當前使用者是否已經登入認證成功了
isFullyAuthenticated() 如果當前使用者既不是一個匿名使用者又不是通過Remember-Me自動登入的則返回true
hasPermission() 使用擴充套件表示式需要實現PermissionEvaluator介面
    /**
     * 管理端資訊
     * @Author Sans
     * @CreateTime 2019/10/2 14:22
     * @Return Map<String,Object> 返回資料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角色可以訪問
     * @Author Sans
     * @CreateTime 2019/10/2 14:22
     * @Return Map<String,Object> 返回資料MAP
     */
    @PreAuthorize("hasAnyRole('ADMIN','USER')")
    @RequestMapping(value = "/list",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角色可以訪問
     * @Author Sans
     * @CreateTime 2019/10/2 14:22
     * @Return Map<String,Object> 返回資料MAP
     */
    @PreAuthorize("hasRole('ADMIN') and hasRole('USER')")
    @RequestMapping(value = "/menuList",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基本可以滿足大部分鑑權需求,但是有時候面對更復雜的場景上述常規表示式無法完成許可權認證,Security也為我們提供瞭解決方案.通過hasPermission()來擴充套件表示式.使用hasPermission()首先要實現PermissionEvaluator介面

/**
 * 自定義許可權註解驗證
 * @Author Sans
 * @CreateTime 2019/10/6 13:31
 */
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private SysUserService sysUserService;
    /**
     * hasPermission鑑權方法
     * 這裡僅僅判斷PreAuthorize註解中的許可權表示式
     * 實際中可以根據業務需求設計資料庫通過targetUrl和permission做更復雜鑑權
     * 當然targetUrl不一定是URL可以是資料Id還可以是管理員標識等,這裡根據需求自行設計
     * @Author Sans
     * @CreateTime 2019/10/6 18:25
     * @Param  authentication  使用者身份(在使用hasPermission表示式時Authentication引數預設會自動帶上)
     * @Param  targetUrl  請求路徑
     * @Param  permission 請求路徑許可權
     * @Return boolean 是否通過
     */
    @Override
    public boolean hasPermission(Authentication authentication,Object permission) {
        return false;
    }
}
複製程式碼

在請求方法上新增hasPermission示例

    /**
     * 擁有sys:user:info許可權可以訪問
     * hasPermission 第一個引數是請求路徑 第二個引數是許可權表示式
     * @Author Sans
     * @CreateTime 2019/10/2 14:22
     * @Return Map<String,Object> 返回資料MAP
     */
    @PreAuthorize("hasPermission('/admin/userList','sys:user:info')")
    @RequestMapping(value = "/userList",Object> userList(){
        Map<String,"擁有sys:user:info許可權都可以檢視");
        result.put("data",sysUserEntityList);
        return ResultUtil.resultSuccess(result);
    }
複製程式碼

hasPermission可以也可以和其他表示式聯合使用

    /**
     * 擁有ADMIN角色和sys:role:info許可權可以訪問
     * @Author Sans
     * @CreateTime 2019/10/2 14:22
     * @Return Map<String,Object> 返回資料MAP
     */
    @PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")
    @RequestMapping(value = "/adminRoleList",Object> adminRoleList(){
        Map<String,Object> result = new HashMap<>();
        List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
        result.put("title","擁有ADMIN角色和sys:role:info許可權可以訪問");
        result.put("data",sysRoleEntityList);
        return ResultUtil.resultSuccess(result);
    }
複製程式碼

七.測試

建立賬戶這裡使用者加密使用了Security推薦的bCryptPasswordEncoder方法

    /**
     * 註冊使用者
     */
    @Test
    public void contextLoads() {
        // 註冊使用者
        SysUserEntity sysUserEntity = new SysUserEntity();
        sysUserEntity.setUsername("sans");
        sysUserEntity.setPassword(bCryptPasswordEncoder.encode("123456"));
        // 設定使用者狀態
        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角色賬號,登入成功後我們會獲取到身份認證的Token

訪問USER角色的介面,把上一步獲取到的Token設定在Headers中,Key為Authorization,我們之前實現的JWTAuthenticationTokenFilter攔截器會根據請求頭中的Authorization獲取並解析Token
使用USER角色Token訪問ADMIN角色的介面,會被拒絕,告知未授權(暫無許可權會進入我們定義的UserAuthAccessDeniedHandler這個類進行處理)
更換ADMIN角色進行登入並訪問ADMIN介面

八.專案原始碼

碼雲:gitee.com/liselotte/s…

GitHub:github.com/xuyulong201…

謝謝大家閱讀,如果喜歡,請收藏點贊,多給些star,文章不足之處,也請給出寶貴意見.