這篇文章給大家介紹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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。