您好,登錄后才能下訂單哦!
這篇文章主要介紹如何使用Spring Boot+Shiro實現權限管理,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一:配置pom.xml文件
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
二:ShiroConfig配置類
@Configuration public class ShiroConfig { @Bean("sessionManager") public SessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(24 * 60 * 60 * 1000); // 開啟會話驗證器 sessionManager.setSessionValidationSchedulerEnabled(true); // 刪除失效的session sessionManager.setDeleteInvalidSessions(true); // 指定sessionId,使用默認的“JSESSIONID” sessionManager.setSessionIdCookieEnabled(true); return sessionManager; } /** * 我們在使用shiro的時候,首先都會先初始化SecurityManager, * 然后往SecurityManager中注入shiro的其他組件,像sessionManager、realm等。 */ @Bean("securityManager") public SecurityManager securityManager(OAuth3Realm oAuth3Realm, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(oAuth3Realm); securityManager.setSessionManager(sessionManager); return securityManager; } @Bean("shiroFilter") public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); // oauth過濾 Map<String, Filter> filters = new HashMap<>(); filters.put("oauth3", new OAuth3Filter()); shiroFilter.setFilters(filters); // 配置可以匿名訪問的地址,可以根據實際情況自己添加,放行一些靜態資源等,anon 表示放行 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); filterMap.put("/app/**", "anon"); filterMap.put("/file/**", "anon"); filterMap.put("/shiro/login", "anon"); filterMap.put("/swagger/**", "anon"); filterMap.put("/v2/api-docs", "anon"); filterMap.put("/swagger-ui.html", "anon"); filterMap.put("/swagger-resources/**", "anon"); filterMap.put("/captcha.jpg", "anon"); // 其它通過自定義的OAuth3Filter進行過濾 filterMap.put("/**", "oauth3"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } @Bean("lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * @Title: defaultAdvisorAutoProxyCreator * @Description: 掃描上下文,尋找所有的Advistor(通知器),將這些Advisor應用到所有符合切入點的Bean中 * @return Spring的一個bean,由Advisor決定對哪些類的方法進行AOP代理。 * @return DefaultAdvisorAutoProxyCreator 返回類型 * @throws */ // @Bean // public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { // DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); // proxyCreator.setProxyTargetClass(true); // return proxyCreator; // } /** * @Title: authorizationAttributeSourceAdvisor * @Description: 開啟shiro aop注解支持 * @param securityManager * @return 參數說明 * @return AuthorizationAttributeSourceAdvisor 返回類型 * @throws */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
其中,如果啟用了DefaultAdvisorAutoProxyCreator的話,會導致二次代理的問題,Realm中的doGetAuthorizationInfo會重復調用2次。
三:自定義Shiro Filter類OAuth3Filter
public class OAuth3Filter extends AuthenticatingFilter { @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { //獲取請求token String token = getRequestToken((HttpServletRequest) request); if(StringUtils.isNullOrEmpty(token)){ return null; } return new OAuth3Token(token); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //現在vue項目中使用axios發送http請求,每次請求都會多一次Request Method: OPTIONS請求,稱為“預檢”請求 if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){ return true; } return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //獲取請求token,如果token不存在,直接返回401 String token = getRequestToken((HttpServletRequest) request); if(StringUtils.isNullOrEmpty(token)){ HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token")); httpResponse.getWriter().print(json); return false; } return executeLogin(request, response); } @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setContentType("application/json;charset=utf-8"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); try { //處理登錄失敗的異常 Throwable throwable = e.getCause() == null ? e : e.getCause(); R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage()); String json = new Gson().toJson(r); httpResponse.getWriter().print(json); } catch (IOException e1) { } return false; } /** * 獲取請求的token */ private String getRequestToken(HttpServletRequest httpRequest){ //從header中獲取token String token = httpRequest.getHeader("token"); //如果header中不存在token,則從參數中獲取token if(StringUtils.isNullOrEmpty(token)){ token = httpRequest.getParameter("token"); } return "112233445566";//token; } }
對于復雜的跨域請求,Vue會首先發送一個OPTIONS請求,進行驗證。需要后端對所有接口統一處理放行OPTIONS方法(即返回200)即可
四:自定義Token
public class OAuth3Token implements AuthenticationToken { private String token; public OAuth3Token(String token) { this.token = token; } /* * 登錄提交的用戶名 * */ @Override public String getPrincipal() { return token; } /* * 只被Subject 知道的秘密值,比如我們登錄提供的密碼 * */ @Override public Object getCredentials() { return token; } }
五:自定義OAuth3Realm
@Component public class OAuth3Realm extends AuthorizingRealm { @Autowired private ShiroService shiroService; /* * 判斷此Realm是否支持此Token */ @Override public boolean supports(AuthenticationToken token) { return token instanceof OAuth3Token; } /* * 提供用戶信息返回權限信息 * */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("=============獲取已登錄用戶,權限信息============"); SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal(); Long userId = user.getUserId(); // 用戶權限列表 Set<String> permsSet = shiroService.getUserPermissions(userId); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permsSet); return info; } /* * 根據token獲取認證信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=============獲取已登錄用戶,用戶信息============"); String accessToken = (String) token.getPrincipal(); // 根據accessToken,查詢用戶信息 SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken); // token失效 if (tokenEntity == null) { throw new IncorrectCredentialsException("token失效,請重新登錄"); } // 查詢用戶信息 SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId()); // 賬號鎖定 if (user.getStatus().equals("00")) { throw new LockedAccountException("賬號已被鎖定,請聯系管理員"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName() // 返回一個唯一的Realm名字 ); return info; } }
shiroService用來從數據庫或者緩存中,查詢用戶信息和權限信息。
六:TokenGenerator
public class TokenGenerator { public static String generateValue() { return generateValue(UUID.randomUUID().toString()); } private static final char[] hexCode = "0123456789abcdef".toCharArray(); public static String toHexString(byte[] data) { if(data == null) { return null; } StringBuilder r = new StringBuilder(data.length*2); for ( byte b : data) { r.append(hexCode[(b >> 4) & 0xF]); r.append(hexCode[(b & 0xF)]); } return r.toString(); } public static String generateValue(String param) { try { MessageDigest algorithm = MessageDigest.getInstance("MD5"); algorithm.reset(); algorithm.update(param.getBytes()); byte[] messageDigest = algorithm.digest(); return toHexString(messageDigest); } catch (Exception e) { e.printStackTrace(); } return ""; } }
七:Controller層Demo
@RestController @RequestMapping("shiro") public class ShiroController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String login() { return "login"; } @RequestMapping(value = "/info", method = RequestMethod.GET) @RequiresPermissions("sys:config:info") public String info() { return "info"; } }
八:登錄代碼
@PostMapping("/sys/login") public Map<String, Object> login(@RequestBody SysLoginForm form)throws IOException { boolean captcha = captchaService.validate(form.getUuid(), form.getCaptcha()); if(!captcha){ return R.error("驗證碼不正確"); } //用戶信息 SysUserEntity user = sysUserService.queryByUserName(form.getUsername()); //賬號不存在、密碼錯誤 if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) { return R.error("賬號或密碼不正確"); } //賬號鎖定 if(Constant.CommonStatus.BANNED.getValue().equals(user.getStatus())){ return R.error("賬號已被鎖定,請聯系管理員"); } //生成token,并保存到數據庫 R r = sysUserTokenService.createToken(user.getUserId()); return r; }
登錄驗證,獨立實現,驗證成功后創建Token,Token放到Redis緩存中。前端頁面請求接口時要帶Token.
九:代碼調用流程
1.登錄成功后,創建Token,后面的接口調用都要傳遞Token
2.接口首先通過shiroFilter過濾,確定是否要進入OAuth3Filter進行處理
3.OAuth3Filter 首先執行isAccessAllowed方法,如果時OPTIONS請求直接放行,否則進行Shiro的executeLogin驗證
4.executeLogin執行的時候會到OAuth3Realm中調用doGetAuthenticationInfo方法,根據Token獲取當前登錄用戶的信息(Token與用戶的關聯,已在第八點自己實現的登錄代碼中實現)
5.executeLogin成功后,會看當前訪問的接口,有無權限注解@RequiresPermissions
6.如果有注解的話,說需要進行權限驗證,Shiro會通過OAuth3Realm的doGetAuthorizationInfo方法,獲取當前用戶的權限進行驗證
以上是“如何使用Spring Boot+Shiro實現權限管理”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。