管她前浪,仍是後浪?php
能浪的浪,纔是好浪!
前端
天天 8:55 更新文章,天天掉億點點頭髮...java
SpringSecurity是一個用於Java 企業級應用程序的安全框架,主要包含用戶認證和用戶受權兩個方面.相比較Shiro而言,Security功能更加的強大,它能夠很容易地擴展以知足更多安全控制方面的需求,但也相對它的學習成本會更高,兩種框架各有利弊.實際開發中仍是要根據業務和項目的需求來決定使用哪種.mysql
JWT是在Web應用中安全傳遞信息的規範,從本質上來講是Token的演變,是一種生成加密用戶身份信息的Token,特別適用於分佈式單點登錄的場景,無需在服務端保存用戶的認證信息,而是直接對Token進行校驗獲取用戶信息,使單點登陸更爲簡單靈活.web
二.項目環境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>
配置以下:spring
# 配置端口 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類等在這裏省略,請參考源碼sql
編寫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; } }
編寫暫無權限處理類apache
/** * @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, HttpServletResponse response, 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, HttpServletResponse response, 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,ResultUtil.resultCode(500,"用戶被凍結")); } 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 @Component public class UserLoginSuccessHandler implements AuthenticationSuccessHandler { /** * 登陸成功返回結果 * @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; // 封裝返回參數 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, 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)); } }四.編寫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() // 開啓跨域 .cors() .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, HttpServletResponse response, 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(暫無權限處理類)中進行處理.這裏舉一些例子,代碼中註釋有對應的描述.
/** * 管理端信息 * @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",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角色能夠訪問 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回數據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基本能夠知足大部分鑑權需求,可是有時候面對更復雜的場景上述常規表示式沒法完成權限認證,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 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; } }
在請求方法上添加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",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權限均可以查看"); 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",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權限能夠訪問"); 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接口