備忘錄九: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(OAuth2Realm oAuth2Realm, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(oAuth2Realm); 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("oauth2", new OAuth2Filter()); 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"); // 其它通過自定義的OAuth2Filter進行過濾 filterMap.put("/**", "oauth2"); 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類OAuth2Filter
public class OAuth2Filter 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 OAuth2Token(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 OAuth2Token implements AuthenticationToken { private String token; public OAuth2Token(String token) { this.token = token; } /* * 登入提交的使用者名稱 * */ @Override public String getPrincipal() { return token; } /* * 只被Subject 知道的祕密值,比如我們登入提供的密碼 * */ @Override public Object getCredentials() { return token; } }
五:自定義OAuth2Realm
@Component public class OAuth2Realm extends AuthorizingRealm { @Autowired private ShiroService shiroService; /* * 判斷此Realm是否支援此Token */ @Override public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; } /* * 提供使用者資訊返回許可權資訊 * */ @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過濾,確定是否要進入OAuth2Filter進行處理
3. OAuth2Filter 首先執行isAccessAllowed方法,如果時OPTIONS請求直接放行,否則進行Shiro的executeLogin驗證
4. executeLogin執行的時候會到OAuth2Realm中呼叫doGetAuthenticationInfo方法,根據Token獲取當前登入使用者的資訊(Token與使用者的關聯,已在第八點自己實現的登入程式碼中實現)
5. executeLogin成功後,會看當前訪問的介面,有無許可權註解@RequiresPermissions
6.如果有註解的話,說需要進行許可權驗證,Shiro會通過 OAuth2Realm的doGetAuthorizationInfo方法,獲取當前使用者的許可權進行驗證
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28624388/viewspace-2660254/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 『學了就忘』Linux許可權管理 — 55、檔案特殊許可權Linux
- SpringSecurity許可權管理系統實戰—九、資料許可權的配置SpringGse
- 『學了就忘』Linux許可權管理 — 56、不可改變位許可權(chattr)Linux
- 『學了就忘』Linux許可權管理 — 54、sudo授權Linux
- MySQL之許可權管理和備份MySql
- 七、許可權管理和資料庫備份資料庫
- Mysql許可權管理以及sql資料備份MySql
- Odoo許可權管理Odoo
- 特殊許可權管理
- sql許可權管理SQL
- 許可權管理策略
- MySQL許可權管理MySql
- 4、許可權管理
- RBAC許可權管理
- PostgreSQL:許可權管理SQL
- spring2 Aop與事務、許可權管理Spring
- linux許可權管理,例項: 建立共享目錄Linux
- django開發之許可權管理(一)——許可權管理詳解(許可權管理原理以及方案)、不使用許可權框架的原始授權方式詳解Django框架
- Linux 筆記分享九:ACL 許可權Linux筆記
- 基於Spring Security實現許可權管理系統Spring
- spring-boot-plus整合Shiro+JWT許可權管理SpringbootJWT
- casbin-許可權管理
- Security 10:許可權管理
- 【自然框架】許可權的視訊演示(二):許可權到欄位、許可權到記錄框架
- spring security許可權認證Spring
- Spring security(五)-完美許可權管理系統(授權過程分析)Spring
- 檔案目錄許可權操作
- Linux 目錄許可權研究Linux
- Android6.0------許可權申請管理(單個許可權和多個許可權申請)Android
- DRF內建許可權元件之自定義許可權管理類元件
- 賬號和許可權管理
- 1.6.1. 管理員許可權
- Linux 中的許可權管理Linux
- ThinkPHP5+許可權管理PHP
- 關於mysql許可權管理MySql
- ubuntu 許可權管理設定Ubuntu
- Linux 下許可權的管理Linux
- MySQL許可權管理實戰MySql