這篇文章給大家介紹Springboot整合Shiro中怎樣進行權限管理,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
Shiro的授權流程跟認證流程類似:
創建SecurityManager
安全管理器
Subject
主體帶授權信息執行授權,請求到SecurityManager
SecurityManager
安全管理器調用Authorizer
授權
Authorizer
結合主體一步步傳過來的授權信息與Realm
中的數據比對,授權
(1)我們已經在ShiroConfig配置類中配置好了SecurityManager
安全管理器
/** * 配置Shiro核心 安全管理器 SecurityManager * SecurityManager安全管理器:所有與安全有關的操作都會與SecurityManager交互;且它管理著所有Subject;負責與后邊介紹的其他組件進行交互。(類似于SpringMVC中的DispatcherServlet控制器) */ @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //將自定義的realm交給SecurityManager管理 securityManager.setRealm(new CustomRealm()); return securityManager; }
(2)下面對自定義Realm類CustomRealm
中的授權方法doGetAuthorizationInfo
進行重寫
@Override /** * 授權 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { //獲取當前登錄的用戶 User user = (User) principal.getPrimaryPrincipal(); //通過SimpleAuthenticationInfo做授權 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加角色 simpleAuthorizationInfo.addRole(user.getRole()); //添加權限 simpleAuthorizationInfo.addStringPermissions(user.getPermissions()); return simpleAuthorizationInfo; }
上面主要通過SimpleAuthorizationInfo
中的addRole
和addStringPermissions
添加當前用戶擁有的角色和權限,與主體的授權信息進行比對。
(3)主體調用授權請求
主體進行授權請求有兩種方式,一種是編程式,一種是注解式。
①編程式:通過Subject
的hasRole()
進行角色的校檢,通過isPermitted()
進行權限的校檢
@GetMapping("/dog") public String dog(){ Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("dog")){ return "dog√"; } else { return "dog×"; } } @GetMapping("/cat") public String cat(){ Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("cat")){ return "cat√"; } else { return "cat×"; } } @GetMapping("/rap") public String rap(){ Subject subject = SecurityUtils.getSubject(); if(subject.isPermitted("rap")){ return "rap"; }else{ return "沒權限你Rap個錘子啊!"; }
模擬用戶數據如下:
/** * 模擬數據庫數據 * @return */ private List<User> getUsers(){ List<User> users = new ArrayList<>(2); List<String> cat = new ArrayList<>(3); cat.add("sing"); cat.add("rap"); List<String> dog = new ArrayList<>(3); dog.add("jump"); dog.add("basketball"); users.add(new User("張小黑的貓","123qwe",true,"cat",cat)); users.add(new User("張小黑的狗","123qwe",true,"dog",dog)); return users; }
測試結果如圖:
authorization.gif
②注解式:
首先需要開啟Aop注解,在ShiroConfig類中新增如下方法:
/** * 開啟aop注解支持 * 即在controller中使用 @RequiresPermissions("user/userList") */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); //設置安全管理器 attributeSourceAdvisor.setSecurityManager(securityManager); return attributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; }
對于未授權的用戶需要進行友好的提示,一般返回特定的403頁面,在ShiroConfig類中新增如下方法實現:
/** * 處理未授權的異常,返回自定義的錯誤頁面(403) * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); /*未授權處理頁*/ properties.setProperty("UnauthorizedException", "403.html"); resolver.setExceptionMappings(properties); return resolver; }
解釋下上面的代碼properties.setProperty("UnauthorizedException","403.html");
用來對抓取到UnauthorizedException
未授權異常進行處理,重定向到403.html
頁面。我們需要創建403頁面403.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未授權</title> </head> <body> 403,您沒有訪問權限 </body> </html>
然后在Controller使用注解@RequiresRoles("xxx")
和@RequiresPermissions("xxx")
進行角色和權限的校檢
@GetMapping("/sing") @RequiresRoles("cat") public String sing(){ return "sing"; } @GetMapping("/jump") @RequiresPermissions("jump") public String jump(){ return "jump"; }
測試結果如圖:
authorization2.gif
總結:一般對未授權用戶需要統一返回指定403頁面的,使用注解更加方便;需要做業務邏輯(比如對未授權的請求記錄進行預警等),使用編程式更加方便。
(4)前端的shiro標簽
通常前端頁面展示需要與用戶的權限對等,即只給用戶看到他們權限內的內容。(比如用戶"張小黑的貓",對應角色“cat”,對應權限“sing”和“rap”,那么該用戶登錄后只展示Cat、sing和rap的按鈕)
通常解決方式有兩種:
其一:登錄后通過讀取數據庫中角色和權限,獲取需要展示的菜單內容,動態的在前端渲染;
其二:所有內容都在前端寫好,通過前端的shiro標簽控制對應權限內容部分的渲染。
這里給大家演示shiro標簽的使用。(前端的shiro標簽有Jsp標簽、Freemarker標簽、Thymeleaf標簽等,演示用的為thymeleaf標簽)
Shiro標簽說明: guest標簽:`<shiro:guest></shiro:guest>`,用戶沒有身份驗證時顯示相應信息,即游客訪問信息。 user標簽:`<shiro:user></shiro:user>`,用戶已經身份驗證/記住我登錄后顯示相應的信息。 authenticated標簽:`<shiro:authenticated></shiro:authenticated>`,用戶已經身份驗證通過,即Subject.login登錄成功,不是記住我登錄的。 notAuthenticated標簽:`<shiro:notAuthenticated></shiro:notAuthenticated>`,用戶已經身份驗證通過,即沒有調用Subject.login進行登錄,包括記住我自動登錄的也屬于未進行身份驗證。 principal標簽:`<shiro: principal/><shiro:principal property="username"/>`,相當`((User)Subject.getPrincipals()).getUsername()`。 lacksPermission標簽:`<shiro:lacksPermission name="org:create"></shiro:lacksPermission>`,如果當前Subject沒有權限將顯示body體內容。 hasRole標簽:`<shiro:hasRole name="admin"></shiro:hasRole>`,如果當前Subject有角色將顯示body體內容。 hasAnyRoles標簽:`<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>`,如果當前Subject有任意一個角色(或的關系)將顯示body體內容。 lacksRole標簽:`<shiro:lacksRole name="abc"></shiro:lacksRole>`,如果當前Subject沒有角色將顯示body體內容。 hasPermission標簽:`<shiro:hasPermission name="user:create"></shiro:hasPermission>`,如果當前Subject有權限將顯示body體內容
使用:
①pom.xml引入相應的依賴
<!-- 兼容于thymeleaf的shiro --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
注意:這里引用的版本是2.0.0,之前1.0.2有兼容問題
②ShiroConfig中加入配置
/** * 用于thymeleaf模板使用shiro標簽 * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }
③前端頁面使用shiro標簽
<!--使用標簽后 --> <shiro:hasRole name="dog"><a href="/dog">Dog</a></shiro:hasRole> <shiro:hasRole name="cat"><a href="/cat">Cat</a></shiro:hasRole> <hr> <shiro:hasPermission name="sing"><a href="/sing">Sing</a></shiro:hasPermission> <shiro:hasPermission name="jump"><a href="/jump">Jump</a></shiro:hasPermission> <shiro:hasPermission name="rap"><a href="/rap">Rap</a></shiro:hasPermission> <shiro:hasPermission name="basketball"><a href="/basketball">Basketball</a></shiro:hasPermission>
注意:使用前現在html標簽內引入shiro標簽,即<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="https://cache.yisu.com/upload/information/20210524/347/782630.gif
----------------------------------------------到這里,shiro授權基本搞定了---------------------------------------------------------
下面附上項目結構和代碼:
image.png
AuthorizationController.java:
package com.cdq.shriodemo.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class AuthorizationController { /** * 1.編程式: * (1)通過Subject的hasRole()進行角色的校檢 * (2)通過isPermitted()進行權限的校檢 */ /** * 1.注解式: * (1)使用注解@RequiresRoles("xxx")進行角色的校檢 * (2)使用注解@RequiresPermissions("xxx")進行權限的校檢 */ //一般對未授權用戶需要統一返回指定403頁面的,使用注解更加方便; // 需要做業務邏輯(比如對未授權的請求記錄進行預警等),使用編程式更加方便。 //編程式 @GetMapping("/dog") public String dog() { Subject subject = SecurityUtils.getSubject(); if (subject.hasRole("dog")) { return "dog√"; } else { return "dog×"; } } //編程式 @GetMapping("/cat") public String cat() { Subject subject = SecurityUtils.getSubject(); if (subject.hasRole("cat")) { return "cat√"; } else { return "cat×"; } } @GetMapping("/sing") @RequiresRoles("cat")//如果subject中有cat角色才可以訪問方法sing。如果沒有這個權限則會拋出異常AuthorizationException。 public String sing() { return "sing"; } @GetMapping("/jump") @RequiresPermissions("jump")//要求subject中必須同時含有jump的權限才能執行方法jump()。 public String jump() { return "jump"; } //編程式 @GetMapping("/rap") public String rap() { Subject subject = SecurityUtils.getSubject(); if (subject.isPermitted("rap")) { return "rap"; } else { return "沒權限你Rap個錘子啊!"; } } //編程式 @GetMapping("/basketball") public String basketball() { Subject subject = SecurityUtils.getSubject(); if (subject.isPermitted("basketball")) { return "basketball"; } else { return "你會打個粑粑球!"; } } }
CustomRealm.java:
package com.cdq.shriodemo.shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.ArrayList; import java.util.List; /** * @Description: shiro的自定義realm * Realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。 * @author ChenDeQuan * @data 2019-05-22 17:51 */ public class CustomRealm extends AuthorizingRealm { @Override /** * 認證 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1.獲取用戶輸入的賬號 String username = (String)token.getPrincipal(); //2.通過username模擬從數據庫中查找到user實體 User user = getUserByUserName(username); if(user == null){ return null; } //3.通過SimpleAuthenticationInfo做身份處理 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),getName()); //4.用戶賬號狀態驗證等其他業務操作 if(!user.getAvailable()){ throw new AuthenticationException("該賬號已經被禁用"); } //5.返回身份處理對象 return simpleAuthenticationInfo; } @Override /** * 授權 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { //獲取當前登錄的用戶 User user = (User) principal.getPrimaryPrincipal(); //通過SimpleAuthenticationInfo做授權 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加角色 simpleAuthorizationInfo.addRole(user.getRole()); //添加權限 simpleAuthorizationInfo.addStringPermissions(user.getPermissions()); return simpleAuthorizationInfo; } /** * 模擬通過username從數據庫中查找到user實體 * @param username * @return */ private User getUserByUserName(String username){ List<User> users = getUsers(); for(User user : users){ if(user.getUsername().equals(username)){ return user; } } return null; } /** * 模擬數據庫數據 * @return */ private List<User> getUsers(){ List<User> users = new ArrayList<>(2); List<String> cat = new ArrayList<>(2); cat.add("sing"); cat.add("rap"); List<String> dog = new ArrayList<>(2); dog.add("jump"); dog.add("basketball"); users.add(new User("cxk1","123",true,"cat",cat)); users.add(new User("cxk2","123",true,"dog",dog)); return users; } }
ShiroConfig.java:
package com.cdq.shriodemo.shiro; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; /** * @Description springboot中的Shiro配置類 * @author ChenDeQuan * @data 2019-05-22 17:17 */ @Configuration public class ShiroConfig { /** * 配置Shiro核心 安全管理器 SecurityManager * SecurityManager安全管理器:所有與安全有關的操作都會與SecurityManager交互;且它管理著所有Subject;負責與后邊介紹的其他組件進行交互。(類似于SpringMVC中的DispatcherServlet控制器) */ @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //將自定義的realm交給SecurityManager管理 securityManager.setRealm(new CustomRealm()); return securityManager; } /** * 配置Shiro的Web過濾器,攔截瀏覽器請求并交給SecurityManager處理 * @return */ @Bean public ShiroFilterFactoryBean webFilter(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //配置攔截鏈 使用LinkedHashMap,因為LinkedHashMap是有序的,shiro會根據添加的順序進行攔截 // Map<K,V> K指的是攔截的url V值的是該url是否攔截 Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16); //配置退出過濾器logout,由shiro實現 filterChainMap.put("/logout","logout"); //authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問,先配置anon再配置authc。 filterChainMap.put("/login","anon"); filterChainMap.put("/**", "authc"); //設置默認登錄的URL. shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; } /** * 開啟aop注解支持 * 即在controller中使用 @RequiresPermissions("user/userList") */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); //設置安全管理器 attributeSourceAdvisor.setSecurityManager(securityManager); return attributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } /** * 處理未授權的異常,返回自定義的錯誤頁面(403) * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); /*未授權處理頁*/ properties.setProperty("UnauthorizedException", "403.html"); resolver.setExceptionMappings(properties); return resolver; } /** * 用于thymeleaf模板使用shiro標簽 * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } }
User.java:
package com.cdq.shriodemo.shiro; import java.util.List; /** * @Description 用戶 * @author ChenDeQuan * @data 2019-05-22 19:18 */ public class User { private String username; private String password; private Boolean available; private String role; private List<String> permissions; public User(String username, String password, Boolean available, String role, List<String> permissions) { this.username = username; this.password = password; this.available = available; this.role = role; this.permissions = permissions; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Boolean getAvailable() { return available; } public void setAvailable(Boolean available) { this.available = available; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public List<String> getPermissions() { return permissions; } public void setPermissions(List<String> permissions) { this.permissions = permissions; } }
success.html:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>success</title> </head> <body> <span th:text="'歡迎你,'+${username}"></span> <br> <!--使用標簽前 --> <!--<a href="/dog">Dog</a>--> <!--<a href="/cat">Cat</a>--> <!--<hr>--> <!--<a href="/sing">Sing</a>--> <!--<a href="/jump">Jump</a>--> <!--<a href="/rap">Rap</a>--> <!--<a href="/basketball">Basketball</a>--> <!--<hr>--> <!--使用標簽后 --> <shiro:hasRole name="dog"><a href="/dog">Dog</a></shiro:hasRole> <shiro:hasRole name="cat"><a href="/cat">Cat</a></shiro:hasRole> <hr> <shiro:hasPermission name="sing"><a href="/sing">Sing</a></shiro:hasPermission> <shiro:hasPermission name="jump"><a href="/jump">Jump</a></shiro:hasPermission> <shiro:hasPermission name="rap"><a href="/rap">Rap</a></shiro:hasPermission> <shiro:hasPermission name="basketball"><a href="/basketball">Basketball</a></shiro:hasPermission> </body> </html>
關于Springboot整合Shiro中怎樣進行權限管理就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。