您好,登錄后才能下訂單哦!
這篇文章給大家介紹Springboot整合Shiro中怎樣進(jìn)行權(quán)限管理,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
Shiro的授權(quán)流程跟認(rèn)證流程類似:
創(chuàng)建SecurityManager
安全管理器
Subject
主體帶授權(quán)信息執(zhí)行授權(quán),請(qǐng)求到SecurityManager
SecurityManager
安全管理器調(diào)用Authorizer
授權(quán)
Authorizer
結(jié)合主體一步步傳過(guò)來(lái)的授權(quán)信息與Realm
中的數(shù)據(jù)比對(duì),授權(quán)
(1)我們已經(jīng)在ShiroConfig配置類中配置好了SecurityManager
安全管理器
/** * 配置Shiro核心 安全管理器 SecurityManager * SecurityManager安全管理器:所有與安全有關(guān)的操作都會(huì)與SecurityManager交互;且它管理著所有Subject;負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互。(類似于SpringMVC中的DispatcherServlet控制器) */ @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //將自定義的realm交給SecurityManager管理 securityManager.setRealm(new CustomRealm()); return securityManager; }
(2)下面對(duì)自定義Realm類CustomRealm
中的授權(quán)方法doGetAuthorizationInfo
進(jìn)行重寫(xiě)
@Override /** * 授權(quán) */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { //獲取當(dāng)前登錄的用戶 User user = (User) principal.getPrimaryPrincipal(); //通過(guò)SimpleAuthenticationInfo做授權(quán) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加角色 simpleAuthorizationInfo.addRole(user.getRole()); //添加權(quán)限 simpleAuthorizationInfo.addStringPermissions(user.getPermissions()); return simpleAuthorizationInfo; }
上面主要通過(guò)SimpleAuthorizationInfo
中的addRole
和addStringPermissions
添加當(dāng)前用戶擁有的角色和權(quán)限,與主體的授權(quán)信息進(jìn)行比對(duì)。
(3)主體調(diào)用授權(quán)請(qǐng)求
主體進(jìn)行授權(quán)請(qǐng)求有兩種方式,一種是編程式,一種是注解式。
①編程式:通過(guò)Subject
的hasRole()
進(jìn)行角色的校檢,通過(guò)isPermitted()
進(jìn)行權(quán)限的校檢
@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 "沒(méi)權(quán)限你Rap個(gè)錘子啊!"; }
模擬用戶數(shù)據(jù)如下:
/** * 模擬數(shù)據(jù)庫(kù)數(shù)據(jù) * @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; }
測(cè)試結(jié)果如圖:
authorization.gif
②注解式:
首先需要開(kāi)啟Aop注解,在ShiroConfig類中新增如下方法:
/** * 開(kāi)啟aop注解支持 * 即在controller中使用 @RequiresPermissions("user/userList") */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); //設(shè)置安全管理器 attributeSourceAdvisor.setSecurityManager(securityManager); return attributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; }
對(duì)于未授權(quán)的用戶需要進(jìn)行友好的提示,一般返回特定的403頁(yè)面,在ShiroConfig類中新增如下方法實(shí)現(xiàn):
/** * 處理未授權(quán)的異常,返回自定義的錯(cuò)誤頁(yè)面(403) * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); /*未授權(quán)處理頁(yè)*/ properties.setProperty("UnauthorizedException", "403.html"); resolver.setExceptionMappings(properties); return resolver; }
解釋下上面的代碼properties.setProperty("UnauthorizedException","403.html");
用來(lái)對(duì)抓取到UnauthorizedException
未授權(quán)異常進(jìn)行處理,重定向到403.html
頁(yè)面。我們需要?jiǎng)?chuàng)建403頁(yè)面403.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未授權(quán)</title> </head> <body> 403,您沒(méi)有訪問(wèn)權(quán)限 </body> </html>
然后在Controller使用注解@RequiresRoles("xxx")
和@RequiresPermissions("xxx")
進(jìn)行角色和權(quán)限的校檢
@GetMapping("/sing") @RequiresRoles("cat") public String sing(){ return "sing"; } @GetMapping("/jump") @RequiresPermissions("jump") public String jump(){ return "jump"; }
測(cè)試結(jié)果如圖:
authorization2.gif
總結(jié):一般對(duì)未授權(quán)用戶需要統(tǒng)一返回指定403頁(yè)面的,使用注解更加方便;需要做業(yè)務(wù)邏輯(比如對(duì)未授權(quán)的請(qǐng)求記錄進(jìn)行預(yù)警等),使用編程式更加方便。
(4)前端的shiro標(biāo)簽
通常前端頁(yè)面展示需要與用戶的權(quán)限對(duì)等,即只給用戶看到他們權(quán)限內(nèi)的內(nèi)容。(比如用戶"張小黑的貓",對(duì)應(yīng)角色“cat”,對(duì)應(yīng)權(quán)限“sing”和“rap”,那么該用戶登錄后只展示Cat、sing和rap的按鈕)
通常解決方式有兩種:
其一:登錄后通過(guò)讀取數(shù)據(jù)庫(kù)中角色和權(quán)限,獲取需要展示的菜單內(nèi)容,動(dòng)態(tài)的在前端渲染;
其二:所有內(nèi)容都在前端寫(xiě)好,通過(guò)前端的shiro標(biāo)簽控制對(duì)應(yīng)權(quán)限內(nèi)容部分的渲染。
這里給大家演示shiro標(biāo)簽的使用。(前端的shiro標(biāo)簽有Jsp標(biāo)簽、Freemarker標(biāo)簽、Thymeleaf標(biāo)簽等,演示用的為thymeleaf標(biāo)簽)
Shiro標(biāo)簽說(shuō)明: guest標(biāo)簽:`<shiro:guest></shiro:guest>`,用戶沒(méi)有身份驗(yàn)證時(shí)顯示相應(yīng)信息,即游客訪問(wèn)信息。 user標(biāo)簽:`<shiro:user></shiro:user>`,用戶已經(jīng)身份驗(yàn)證/記住我登錄后顯示相應(yīng)的信息。 authenticated標(biāo)簽:`<shiro:authenticated></shiro:authenticated>`,用戶已經(jīng)身份驗(yàn)證通過(guò),即Subject.login登錄成功,不是記住我登錄的。 notAuthenticated標(biāo)簽:`<shiro:notAuthenticated></shiro:notAuthenticated>`,用戶已經(jīng)身份驗(yàn)證通過(guò),即沒(méi)有調(diào)用Subject.login進(jìn)行登錄,包括記住我自動(dòng)登錄的也屬于未進(jìn)行身份驗(yàn)證。 principal標(biāo)簽:`<shiro: principal/><shiro:principal property="username"/>`,相當(dāng)`((User)Subject.getPrincipals()).getUsername()`。 lacksPermission標(biāo)簽:`<shiro:lacksPermission name="org:create"></shiro:lacksPermission>`,如果當(dāng)前Subject沒(méi)有權(quán)限將顯示body體內(nèi)容。 hasRole標(biāo)簽:`<shiro:hasRole name="admin"></shiro:hasRole>`,如果當(dāng)前Subject有角色將顯示body體內(nèi)容。 hasAnyRoles標(biāo)簽:`<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>`,如果當(dāng)前Subject有任意一個(gè)角色(或的關(guān)系)將顯示body體內(nèi)容。 lacksRole標(biāo)簽:`<shiro:lacksRole name="abc"></shiro:lacksRole>`,如果當(dāng)前Subject沒(méi)有角色將顯示body體內(nèi)容。 hasPermission標(biāo)簽:`<shiro:hasPermission name="user:create"></shiro:hasPermission>`,如果當(dāng)前Subject有權(quán)限將顯示body體內(nèi)容
使用:
①pom.xml引入相應(yīng)的依賴
<!-- 兼容于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有兼容問(wèn)題
②ShiroConfig中加入配置
/** * 用于thymeleaf模板使用shiro標(biāo)簽 * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }
③前端頁(yè)面使用shiro標(biāo)簽
<!--使用標(biāo)簽后 --> <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>
注意:使用前現(xiàn)在html標(biāo)簽內(nèi)引入shiro標(biāo)簽,即<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="https://cache.yisu.com/upload/information/20210524/347/782630.gif
----------------------------------------------到這里,shiro授權(quán)基本搞定了---------------------------------------------------------
下面附上項(xiàng)目結(jié)構(gòu)和代碼:
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)通過(guò)Subject的hasRole()進(jìn)行角色的校檢 * (2)通過(guò)isPermitted()進(jìn)行權(quán)限的校檢 */ /** * 1.注解式: * (1)使用注解@RequiresRoles("xxx")進(jìn)行角色的校檢 * (2)使用注解@RequiresPermissions("xxx")進(jìn)行權(quán)限的校檢 */ //一般對(duì)未授權(quán)用戶需要統(tǒng)一返回指定403頁(yè)面的,使用注解更加方便; // 需要做業(yè)務(wù)邏輯(比如對(duì)未授權(quán)的請(qǐng)求記錄進(jìn)行預(yù)警等),使用編程式更加方便。 //編程式 @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角色才可以訪問(wèn)方法sing。如果沒(méi)有這個(gè)權(quán)限則會(huì)拋出異常AuthorizationException。 public String sing() { return "sing"; } @GetMapping("/jump") @RequiresPermissions("jump")//要求subject中必須同時(shí)含有jump的權(quán)限才能執(zhí)行方法jump()。 public String jump() { return "jump"; } //編程式 @GetMapping("/rap") public String rap() { Subject subject = SecurityUtils.getSubject(); if (subject.isPermitted("rap")) { return "rap"; } else { return "沒(méi)權(quán)限你Rap個(gè)錘子啊!"; } } //編程式 @GetMapping("/basketball") public String basketball() { Subject subject = SecurityUtils.getSubject(); if (subject.isPermitted("basketball")) { return "basketball"; } else { return "你會(huì)打個(gè)粑粑球!"; } } }
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獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說(shuō)SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。 * @author ChenDeQuan * @data 2019-05-22 17:51 */ public class CustomRealm extends AuthorizingRealm { @Override /** * 認(rèn)證 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1.獲取用戶輸入的賬號(hào) String username = (String)token.getPrincipal(); //2.通過(guò)username模擬從數(shù)據(jù)庫(kù)中查找到user實(shí)體 User user = getUserByUserName(username); if(user == null){ return null; } //3.通過(guò)SimpleAuthenticationInfo做身份處理 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),getName()); //4.用戶賬號(hào)狀態(tài)驗(yàn)證等其他業(yè)務(wù)操作 if(!user.getAvailable()){ throw new AuthenticationException("該賬號(hào)已經(jīng)被禁用"); } //5.返回身份處理對(duì)象 return simpleAuthenticationInfo; } @Override /** * 授權(quán) */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { //獲取當(dāng)前登錄的用戶 User user = (User) principal.getPrimaryPrincipal(); //通過(guò)SimpleAuthenticationInfo做授權(quán) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加角色 simpleAuthorizationInfo.addRole(user.getRole()); //添加權(quán)限 simpleAuthorizationInfo.addStringPermissions(user.getPermissions()); return simpleAuthorizationInfo; } /** * 模擬通過(guò)username從數(shù)據(jù)庫(kù)中查找到user實(shí)體 * @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; } /** * 模擬數(shù)據(jù)庫(kù)數(shù)據(jù) * @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安全管理器:所有與安全有關(guān)的操作都會(huì)與SecurityManager交互;且它管理著所有Subject;負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互。(類似于SpringMVC中的DispatcherServlet控制器) */ @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //將自定義的realm交給SecurityManager管理 securityManager.setRealm(new CustomRealm()); return securityManager; } /** * 配置Shiro的Web過(guò)濾器,攔截瀏覽器請(qǐng)求并交給SecurityManager處理 * @return */ @Bean public ShiroFilterFactoryBean webFilter(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設(shè)置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //配置攔截鏈 使用LinkedHashMap,因?yàn)長(zhǎng)inkedHashMap是有序的,shiro會(huì)根據(jù)添加的順序進(jìn)行攔截 // Map<K,V> K指的是攔截的url V值的是該url是否攔截 Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16); //配置退出過(guò)濾器logout,由shiro實(shí)現(xiàn) filterChainMap.put("/logout","logout"); //authc:所有url都必須認(rèn)證通過(guò)才可以訪問(wèn); anon:所有url都都可以匿名訪問(wèn),先配置anon再配置authc。 filterChainMap.put("/login","anon"); filterChainMap.put("/**", "authc"); //設(shè)置默認(rèn)登錄的URL. shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; } /** * 開(kāi)啟aop注解支持 * 即在controller中使用 @RequiresPermissions("user/userList") */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); //設(shè)置安全管理器 attributeSourceAdvisor.setSecurityManager(securityManager); return attributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } /** * 處理未授權(quán)的異常,返回自定義的錯(cuò)誤頁(yè)面(403) * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); /*未授權(quán)處理頁(yè)*/ properties.setProperty("UnauthorizedException", "403.html"); resolver.setExceptionMappings(properties); return resolver; } /** * 用于thymeleaf模板使用shiro標(biāo)簽 * @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> <!--使用標(biāo)簽前 --> <!--<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>--> <!--使用標(biāo)簽后 --> <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>
關(guān)于Springboot整合Shiro中怎樣進(jìn)行權(quán)限管理就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。