javascript
@builder注解_SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证
一.說(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)單靈活.
二.項(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.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-securityartifactId> dependency> <dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-boot-starterartifactId> <version>3.1.0version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>druidartifactId> <version>1.1.6version> dependency> <dependency> <groupId>org.apache.commonsgroupId> <artifactId>commons-lang3artifactId> <version>3.5version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.45version> dependency> <dependency> <groupId>org.springframework.securitygroupId> <artifactId>spring-security-jwtartifactId> <version>1.0.9.RELEASEversion> dependency> <dependency> <groupId>io.jsonwebtokengroupId> <artifactId>jjwtartifactId> <version>0.9.0version> dependency>dependencies>配置如下:
# 配置端口server: port: 8764spring: # 配置數(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: #主鍵類型 AUTO:"數(shù)據(jù)庫(kù)ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數(shù)字類型唯一ID)", UUID:"全局唯一ID UUID"; id-type: AUTO #字段策略 IGNORED:"忽略判斷" NOT_NULL:"非 NULL 判斷") NOT_EMPTY:"非空判斷" field-strategy: NOT_EMPTY #數(shù)據(jù)庫(kù)類型 db-type: MYSQL configuration: # 是否開(kāi)啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫(kù)列名到Java屬性駝峰命名的類似映射 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àng)目基礎(chǔ)類
Entity,Dao,Service,及等SpringSecurity用戶的Entity,Service類等在這里省略,請(qǐng)參考源碼
編寫JWT工具類
/** * JWT工具類 * @Author Sans * @CreateTime 2019/10/2 7:42 */@Slf4jpublic 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; }}編寫暫無(wú)權(quán)限處理類
/** * @Description 暫無(wú)權(quán)限處理類 * @Author Sans * @CreateTime 2019/10/3 8:39 */@Componentpublic 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)")); }}編寫用戶未登錄處理類
/** * 用戶未登錄處理類 * @Author Sans * @CreateTime 2019/10/3 8:55 */@Componentpublic class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint { /** * 用戶未登錄返回結(jié)果 * @Author Sans * @CreateTime 2019/10/3 9:01 */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登錄")); }}編寫登錄失敗處理類
/** * @Description 登錄失敗處理類 * @Author Sans * @CreateTime 2019/10/3 9:06 */@Slf4j@Componentpublic class UserLoginFailureHandler implements AuthenticationFailureHandler { /** * 登錄失敗返回結(jié)果 * @Author Sans * @CreateTime 2019/10/3 9:12 */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ // 這些對(duì)于操作的處理類可以根據(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,"登錄失敗")); }}編寫登錄成功處理類
/** * @Description 登錄成功處理類 * @Author Sans * @CreateTime 2019/10/3 9:13 */@Slf4j@Componentpublic 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 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 */@Componentpublic 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 resultData = new HashMap<>(); resultData.put("code","200"); resultData.put("msg", "登出成功"); SecurityContextHolder.clearContext(); ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData)); }}四.編寫Security核心類
編寫自定義登錄驗(yàn)證類
/** * 自定義登錄驗(yàn)證 * @Author Sans * @CreateTime 2019/10/1 19:11 */@Componentpublic 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 authorities = new HashSet<>(); // 查詢用戶角色 List 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; }}編寫自定義PermissionEvaluator注解驗(yàn)證
/** * 自定義權(quán)限注解驗(yàn)證 * @Author Sans * @CreateTime 2019/10/6 13:31 */@Componentpublic 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 permissions = new HashSet<>(); List 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; }}編寫SpringSecurity核心配置類
/** * SpringSecurity核心配置類 * @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; /** * 自定義注銷成功處理器 */ @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() //配置未登錄自定義處理類 .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler) .and() //配置登錄地址 .formLogin() .loginProcessingUrl("/login/userLogin") //配置登錄成功自定義處理類 .successHandler(userLoginSuccessHandler) //配置登錄失敗自定義處理類 .failureHandler(userLoginFailureHandler) .and() //配置登出地址 .logout() .logoutUrl("/login/userLogout") //配置用戶登出自定義處理類 .logoutSuccessHandler(userLogoutSuccessHandler) .and() //配置沒(méi)有權(quán)限自定義處理類 .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())); }}五.編寫JWT攔截類
編寫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 */@Slf4jpublic 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 authorities = new ArrayList<>(); String authority = claims.get("authorities").toString(); if(!StringUtils.isEmpty(authority)){ ListString, 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)限處理類)中進(jìn)行處理.這里舉一些例子,代碼中注釋有對(duì)應(yīng)的描述.
/** * 管理端信息 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map 返回?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 返回?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 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 返回?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 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 */@Componentpublic 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 permissions = new HashSet<>(); List 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 返回?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 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 返回?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 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è)類進(jìn)行處理)
更換ADMIN角色進(jìn)行登錄并訪問(wèn)ADMIN接口
今天就講到這里吧,如果覺(jué)得不錯(cuò),記得點(diǎn)贊轉(zhuǎn)發(fā)支持一波,謝謝~~~
作者:Sans_
來(lái)源:juejin.im/post/5da82f066fb9a04e2a73daec
總結(jié)
以上是生活随笔為你收集整理的@builder注解_SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 贵霜帝国是哪个国家(贵霜帝国有多大)
- 下一篇: 电脑系统ip地址无法固定(电脑无法指定i