javascript
SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (三) 鉴权
項目Github地址:?https://github.com/baiye21/ShiroDemo
SpringBoot 基于Shiro + Jwt + Redis的用戶權限管理 (一) 簡介與配置
SpringBoot 基于Shiro + Jwt + Redis的用戶權限管理 (二) 認證?
SpringBoot 基于Shiro + Jwt + Redis的用戶權限管理 (三) 鑒權
一,ShiroFilterFactoryBean配置
FilterChainDefinitionMap排除需要權限才能訪問的URL,這樣才能進入自定義過濾器JwtFilter中進行后續驗證。
比如,測試權限的URl為/role/OneRole.do,它不需要寫入FilterChainDefinitionMap中,如下圖
?二,自定義ShiroSessionManager
認證成功后,將shiro生成的sessionid保存到了access_token中,所以攜帶token訪問時,需要從token將其取出。
com.demo.config.ShiroConfig中
/*** SecurityManager 是 Shiro 架構的核心,通過它來鏈接Realm和用戶(文檔中稱之為Subject.)*/@Beanpublic SecurityManager securityManager(@Qualifier("passwordRealm") PasswordRealm passwordRealm,@Qualifier("jwtRealm") JwtRealm jwtRealm,@Qualifier("demoRealm") DemoRealm demoRealm,@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 設置realmsecurityManager.setAuthenticator(userModularRealmAuthenticator);List<Realm> realms = new ArrayList<>();// 添加多個realmrealms.add(passwordRealm);realms.add(jwtRealm);realms.add(demoRealm);/** 關閉shiro自帶的session,詳情見文檔* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29*/DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();// 不需要將 Shiro Session 中的東西存到任何地方(包括 Http Session 中)defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);securityManager.setSubjectDAO(subjectDAO);// 自定義sessionManagersecurityManager.setSessionManager(shiroSessionManager());// securityManager設置自定義認證規則securityManager.setRealms(realms);return securityManager;}@Beanpublic ShiroSessionManager shiroSessionManager() {ShiroSessionManager shiroSessionManager = new ShiroSessionManager();// TODO redis 配置session持久化shiroSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());return shiroSessionManager;}com.demo.shiro.ShiroSessionManager
@Slf4j public class ShiroSessionManager extends DefaultWebSessionManager {public ShiroSessionManager() {super();}private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";@Overrideprotected Serializable getSessionId(ServletRequest request, ServletResponse response) {// 如果請求頭中有 AuthorizationString token = WebUtils.toHttp(request).getHeader(Const.TOKEN_HEADER_NAME);if (!StringUtils.isEmpty(token)) {if (JwtUtil.verify(token, Const.TOKEN_SECRET)) {String id = JwtUtil.getClaim(token, Const.JSESSIONID);log.debug("ShiroSessionManager從http header 取出token中的JSESSIONID:{}", id);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return id;}return super.getSessionId(request, response);} else {// 否則按默認規則從cookie取sessionIdreturn super.getSessionId(request, response);}}}三,JwtFilter執行executeLogin();
/*** 執行登錄認證* * @param request* @param response* @param mappedValue* @return 是否成功*/@Override// 這個方法判斷 嘗試進行登錄的操作,如果token存在,那么進行提交登錄,如果不存在說明可能是正在進行登錄或者做其它的事情 直接放過即可protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {executeLogin(request, response);return true;} catch (Exception e) {// return false;// throw new AuthenticationException("Token失效請重新登錄");// 認證出現異常,傳遞錯誤信息msgString msg = e.getMessage();// 獲取應用異常(該Cause是導致拋出此throwable(異常)的throwable(異常))Throwable throwable = e.getCause();if (throwable != null && throwable instanceof SignatureVerificationException) {// 該異常為JWT的AccessToken認證失敗(Token或者密鑰不正確)msg = "token或者密鑰不正確(" + throwable.getMessage() + ")";} else if (throwable != null && throwable instanceof TokenExpiredException) {// 該異常為JWT的AccessToken已過期(TokenExpiredException),// 判斷RefreshToken未過期就進行AccessToken刷新if (this.refreshToken(request, response)) {return true;} else {msg = "token已過期(" + throwable.getMessage() + ")";}} else {// 應用異常不為空if (throwable != null) {// 獲取應用異常msgmsg = throwable.getMessage();}}/*** 錯誤兩種處理方式* 1. 將非法請求轉發到/401的Controller處理,拋出自定義無權訪問異常被全局捕捉再返回Response信息* 2. 無需轉發,直接返回Response信息 一般使用第二種(更方便)*/// 直接返回Response信息this.response401(request, response, msg);return false;}}四,UserModularRealmAuthenticator加載DemoRealm
login的時候創建的是CustomizedToken,之后的請求Header攜帶的都是JwtToken,因此區分加載哪一種類型的Realm。
if(authenticationToken instanceof JwtToken) {log.debug("驗證的Token類型是:{}", "JwtToken");typeRealms.clear();// 獲取header部的token進行強制類型轉換JwtToken jwtToken = (JwtToken) authenticationToken;for (Realm realm : realms) {if (realm.getName().contains("Demo")) {typeRealms.add(realm);}}return doSingleRealmAuthentication(typeRealms.iterator().next(), jwtToken);}五,DemoRealm進行鑒權(doGetAuthorizationInfo)
/*** 功能: 獲取用戶權限信息,包括角色以及權限。只有當觸發檢測用戶權限時才會調用此方法,例如checkRole,checkPermission** @param principals* @return AuthorizationInfo 權限信息*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {log.info("demoRealm doGetAuthorizationInfo 用戶賦權 ");String userid = null;if (principals != null) {// 此處的principals為 UserMasterObject PrimaryPrincipal = principals.getPrimaryPrincipal();if (PrimaryPrincipal instanceof UserMaster) {UserMaster userMaster = (UserMaster) PrimaryPrincipal;userid = userMaster.getUserId();} else {// 此處的principals為tokenuserid = JwtUtil.getClaim(principals.toString(), Const.TOKEN_CLAIM_USERID);}}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 獲取用戶角色與權限信息UserAccessInfo userAccessInfo = iUserService.getUserAccessInfo(userid);/*** 設置用戶擁有的角色集合,<br>* * accountType = 1 管理員 admin <br>* accountType = 2 領導 Leader <br>* accountType = 3 普通用戶 user <br>* */info.setRoles(userAccessInfo.getRoleSet());// 設置用戶擁有的權限集合info.addStringPermissions(userAccessInfo.getPermissionSet());return info;}六,getUserAccessInfo
com.demo.service.impl.UserServiceImpl的getUserAccessInfo方法
/*** 獲取用戶角色set與權限set** @param userId* @return*/public UserAccessInfo getUserAccessInfo(String userId) {UserAccessInfo userAccessInfo = new UserAccessInfo();// 用戶角色setSet<String> roleSet = new HashSet<String>();// 用戶權限setSet<String> permissionSet = new HashSet<String>();UserMaster userMaster = userMasterMapper.selectByUserId(userId);// 賬號類型String accountType = userMaster.getAccountType();// 管理員 accountType = 1if (Const.ADMIN_USER_CODE.equals(accountType)) {// adminroleSet.add(Const.ADMIN_USER);// Leader accountType = 2} else if (Const.LEADWER_USER_CODE.equals(accountType)) {// LeaderroleSet.add(Const.LEADER_USER);// 普通用戶 accountType = 3} else if (Const.NORMAL_USER_CODE.equals(accountType)) {// userroleSet.add(Const.NORMAL_USER);}// 用戶權限類型String permissionType = userMaster.getPermissionType();if (Const.LEVEL_001_CODE.equals(permissionType)) {permissionSet.add(Const.LEVEL_001);} else if (Const.LEVEL_002_CODE.equals(permissionType)) {permissionSet.add(Const.LEVEL_002);} else if (Const.LEVEL_003_CODE.equals(permissionType)) {permissionSet.add(Const.LEVEL_003);} else if (Const.LEVEL_004_CODE.equals(permissionType)) {permissionSet.add(Const.LEVEL_004);} else if (Const.LEVEL_005_CODE.equals(permissionType)) {permissionSet.add(Const.LEVEL_005);}userAccessInfo.setRoleSet(roleSet);userAccessInfo.setPermissionSet(permissionSet);return userAccessInfo;}七,簡單測試
測試用的TestRoleController ,主要測試以下權限:
ps:由于之前測試用戶表設計一個用戶只對應一種角色一種權限,所以第四個測試只有失敗的情況
準備的測試用戶
使用demo002用戶登錄
localhost:9999/role/OneRole.do
localhost:9999/role/TwoRole.do
?localhost:9999/role/OnePermission.do
?localhost:9999/role/TwoPermission.do
總結
以上是生活随笔為你收集整理的SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (三) 鉴权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 易语言多线程封装线程启动优先权设置
- 下一篇: PMC亮相IDF展示12G SAS分层存