一、前言
在所有的開發(fā)的系統(tǒng)中,都必須做認(rèn)證(authentication)和授權(quán)(authorization),以保證系統(tǒng)的安全性??紤]到很多讀者對認(rèn)證和授權(quán)有點(diǎn)分不清楚。
authentication [?,θ?nt?'ke??n] 認(rèn)證
authorization [,?θ?r?'ze??n] 授權(quán)
1.1 以坐飛機(jī)舉例子:
- 【認(rèn)證】你要登機(jī),你需要出示你的 passport 和 ticket,passport 是為了證明你張三確實(shí)是你張三,這就是 authentication。
- 【授權(quán)】而機(jī)票是為了證明你張三確實(shí)買了票可以上飛機(jī),這就是 authorization。
1.2 以論壇舉例子:
- 【認(rèn)證】你要登錄論壇,輸入用戶名張三,密碼 1234,密碼正確,證明你張三確實(shí)是張三,這就是 authentication。
- 【授權(quán)】再一 check 用戶張三是個版主,所以有權(quán)限加精刪別人帖,這就是 authorization 。
所以簡單來說:認(rèn)證解決“你是誰”的問題,授權(quán)解決“你能做什么”的問題。另外,在推薦閱讀下《認(rèn)證、授權(quán)、鑒權(quán)和權(quán)限控制》 文章,更加詳細(xì)明確。
在 Java 生態(tài)中,目前有 Spring Security 和 Apache Shiro 兩個安全框架,可以完成認(rèn)證和授權(quán)的功能。本文,我們再來學(xué)習(xí)下 Apache Shiro 。其官方對自己介紹如下:
Apache Shiro? 是一個功能強(qiáng)大且易于使用的 Java 安全框架,它可以提供身份驗(yàn)證、授權(quán)、加密和會話管理的功能。
通過 Shiro 易于理解的 API ,你可以快速、輕松地保護(hù)任何應(yīng)用程序 —— 從最小的移動端應(yīng)用程序到大型的的 Web 和企業(yè)級應(yīng)用程序。
二、 快速入門
2.1 引入依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Apache-shiro</artifactId>
<dependencies>
<!-- 實(shí)現(xiàn)對 Spring MVC 的自動化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 實(shí)現(xiàn)對 Shiro 的自動化配置 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
</project>
shiro-spring-boot-starter依賴對 Shiro 的自動化配置基本沒啥用,需要下面的這個類ShiroConfig自己來主動實(shí)現(xiàn)對 Shiro 的配置。
2.2 ShiroConfig
實(shí)現(xiàn) Shiro 的自定義配置。代碼如下:
package com.erbadagang.springboot.shiro.config;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public Realm realm() {
// 創(chuàng)建 SimpleAccountRealm 對象
SimpleAccountRealm realm = new SimpleAccountRealm();
// 添加兩個用戶。參數(shù)分別是 username、password、roles 。
realm.addAccount("admin", "admin", "ADMIN");
realm.addAccount("normal", "normal", "NORMAL");
return realm;
}
@Bean
public DefaultWebSecurityManager securityManager() {
// 創(chuàng)建 DefaultWebSecurityManager 對象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置其使用的 Realm
securityManager.setRealm(this.realm());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
// 創(chuàng)建 ShiroFilterFactoryBean 對象,用于創(chuàng)建 ShiroFilter 過濾器
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
// 設(shè)置 SecurityManager
filterFactoryBean.setSecurityManager(this.securityManager());
// 設(shè)置 URL 們
filterFactoryBean.setLoginUrl("/login"); // 登陸 URL
filterFactoryBean.setSuccessUrl("/login_success"); // 登陸成功 URL
filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 無權(quán)限 URL
// 設(shè)置 URL 的權(quán)限配置
filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap());
return filterFactoryBean;
}
private Map<String, String> filterChainDefinitionMap() {
Map<String, String> filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,順序匹配
filterMap.put("/test/echo", "anon"); // 允許匿名訪問
filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色
filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色
filterMap.put("/logout", "logout"); // 退出
filterMap.put("/**", "authc"); // 默認(rèn)剩余的 URL ,需要經(jīng)過認(rèn)證
return filterMap;
}
}
一共有三個 Bean 的配置,我們逐個來看看。
2.2.1 Realm
我們先來看看 Realm 的定義?!吧矸蒡?yàn)證”(認(rèn)證)和“授權(quán)”,這個就是 Realm 的職責(zé)。

- Realm 接口,主要定義了“認(rèn)證”方法。代碼如下:
// Realm.java
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
- AuthorizingRealm 抽象類,主要額外定義了授權(quán)方法。代碼如下:
// AuthorizingRealm.java
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
- AuthorizingRealm 同時實(shí)現(xiàn)了 Authorizer 接口,提供判斷經(jīng)過認(rèn)證過的 Subject 是否具有指定的角色、權(quán)限的方法。
- 從圖中我們可以看出,Shiro 提供了多種 AuthorizingRealm 的實(shí)現(xiàn)類,提供從不同的數(shù)據(jù)源獲取數(shù)據(jù)。不過一般在項目中,我們會自定義實(shí)現(xiàn) AuthorizingRealm ,從自己定義的表結(jié)構(gòu)中讀取用戶、角色、權(quán)限等數(shù)據(jù)。雖然說,Shiro 提供了 JdbcRealm 可以訪問數(shù)據(jù)庫,但是它的表結(jié)構(gòu)是固定的,所說我們才要自定義定義實(shí)現(xiàn) AuthorizingRealm 。
本示例中,在 #realm() 方法,我們創(chuàng)建了 SimpleAccountRealm Bean 對象。代碼如上所示:
- SimpleAccountRealm 是使用內(nèi)存作為數(shù)據(jù)源,我們可以手動往里面添加用戶、角色、權(quán)限等數(shù)據(jù)。畢竟作為一個示例,不想引入數(shù)據(jù)庫,增加復(fù)雜性。不過我們在后續(xù)文章中,我們會看到我們使用自定義的 AuthorizingRealm 實(shí)現(xiàn)類。
- 在該方法里,我們添加了「admin/admin」和「normal/normal」兩個用戶,分別對應(yīng) ADMIN 和 NORMAL 角色。
2.2.2 SecurityManager
我們再來看看 SecurityManager 的定義,SecurityManager 是 Shiro 架構(gòu)的核心,配合內(nèi)部安全組件共同組成安全傘。
本示例中,在 #securityManager() 方法,我們創(chuàng)建了 DefaultWebSecurityManager Bean 對象。代碼如下:
// ShiroConfig.java
@Bean
public DefaultWebSecurityManager securityManager() {
// 創(chuàng)建 DefaultWebSecurityManager 對象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置其使用的 Realm
securityManager.setRealm(this.realm());
return securityManager;
}
- 不用特別去糾結(jié) SecurityManager ,創(chuàng)建好 DefaultWebSecurityManager Bean 就完事了~等后續(xù)我們?nèi)腴T完 Shiro 之后,胖友可以在慢慢細(xì)細(xì)去研究。
2.2.3 ShiroFilter
通過 AbstractShiroFilter 過濾器,實(shí)現(xiàn)對請求的攔截,從而實(shí)現(xiàn) Shiro 的功能。AbstractShiroFilter 整體的類圖如下:

本示例中,在 #shiroFilterFactoryBean() 方法,我們創(chuàng)建了 ShiroFilterFactoryBean Bean 對象。代碼如下:
// ShiroConfig.java
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
// <1> 創(chuàng)建 ShiroFilterFactoryBean 對象,用于創(chuàng)建 ShiroFilter 過濾器
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
// <2> 設(shè)置 SecurityManager
filterFactoryBean.setSecurityManager(this.securityManager());
// <3> 設(shè)置 URL 們
filterFactoryBean.setLoginUrl("/login"); // 登錄 URL
filterFactoryBean.setSuccessUrl("/login_success"); // 登錄成功 URL
filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 無權(quán)限 URL
// <4> 設(shè)置 URL 的權(quán)限配置
filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap());
return filterFactoryBean;
}
<1>處,創(chuàng)建 ShiroFilterFactoryBean 對象,用于創(chuàng)建 SpringShiroFilter 過濾器。<2>處,設(shè)置其 SecurityManager 屬性。-
<3>處,設(shè)置各種 URL 。-
#setLoginUrl(String loginUrl)方法,設(shè)置登錄 URL 。在 Shiro 中,約定GET loginUrl為登錄頁面,POST loginUrl為登錄請求。 -
#setSuccessUrl(String successUrl)方法,設(shè)置登錄成功 URL 。在登錄成功時,會重定向到該 URL 上。 -
#etUnauthorizedUrl(String unauthorizedUrl)方法,設(shè)置無權(quán)限的 URL 。在請求校驗(yàn)權(quán)限不通過時,會重定向到該 URL 上。 - 上述的 URL 對應(yīng)的接口,都需要我們自己來實(shí)現(xiàn)。具體可見「2.3 SecurityController」小節(jié)。
-
<4>處,調(diào)用#setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap)方法,設(shè)置 URL 的權(quán)限配置。
在看 #filterChainDefinitionMap() 方法的具體 URL 的權(quán)限配置之前,我們先來了解下 Shiro 內(nèi)置的過濾器們。在 Shiro DefaultFilter 枚舉類中,枚舉了這些過濾器,以及其配置名。整理表格如下:
比較常用的過來器有:
-
anon:AnonymousFilter :允許匿名訪問,即無需登錄。 -
authc:FormAuthenticationFilter :需要經(jīng)過認(rèn)證的用戶,才可以訪問。如果是匿名用戶,則根據(jù) URL 不同,會有不同的處理:- 如果攔截的 URL 是
GET loginUrl登錄頁面,則進(jìn)行該請求,跳轉(zhuǎn)到登錄頁面。 - 如果攔截的 URL 是
POST loginUrl登錄請求,則基于請求表單的username、password進(jìn)行認(rèn)證。認(rèn)證通過后,默認(rèn)重定向到GET loginSuccessUrl地址。 - 如果攔截的 URL 是其它 URL 時,則記錄該 URL 到 Session 中。在用戶登錄成功后,重定向到該 URL 上。
- 如果攔截的 URL 是
-
logout:LogoutFilter :攔截的 URL ,執(zhí)行退出操作。退出完成后,重定向到GET loginUrl登錄頁面。 -
roles:RolesAuthorizationFilter :擁有指定角色的用戶可訪問。 -
perms:PermissionsAuthorizationFilter :擁有指定權(quán)限的用戶可以訪問。
下面,讓我們回過頭來看看 #filterChainDefinitionMap() 方法的具體 URL 的權(quán)限配置。代碼如下:
private Map<String, String> filterChainDefinitionMap() {
Map<String, String> filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,順序匹配
filterMap.put("/test/echo", "anon"); // 允許匿名訪問
filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色
filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色
filterMap.put("/logout", "logout"); // 退出
filterMap.put("/**", "authc"); // 默認(rèn)剩余的 URL ,需要經(jīng)過認(rèn)證
return filterMap;
}
-
/test/echo:我們設(shè)置為anon,允許匿名訪問。 -
/test/admin和/test/normal:我們設(shè)置為roles[...],需要指定角色的用戶可以訪問。其中...處為需要添加的角色名。 -
/logout:我們設(shè)置為logout,實(shí)現(xiàn)退出操作。 -
/**:剩余的 URL ,我們設(shè)置為authc,需要登錄的用戶才可以訪問。同時,對于loginUrl需要執(zhí)行登錄相關(guān)的攔截。
另外,這里在補(bǔ)充一點(diǎn),請求在 ShiroFilter 攔截之后,會根據(jù)該請求的情況,匹配到配置的內(nèi)置的 Shiro Filter 們,逐個進(jìn)行處理。也就是說,ShiroFilter 實(shí)際內(nèi)部有一個由 內(nèi)置的 Shiro Filter 組成的過濾器鏈。
至此,我們已經(jīng)完成了 Shiro 的自定義配置。雖然篇幅有點(diǎn)長,但是可以等我們跑完整個示例之后,再自己回過頭來看看,會發(fā)現(xiàn)還是比較清晰明了的。
2.3 SecurityController
提供登錄、登錄成功等接口。代碼如下:
package com.erbadagang.springboot.shiro.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/")
public class SecurityController {
private Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/login")
public String loginPage() {
return "login.html";
}
@ResponseBody
@PostMapping("/login")
public String login(HttpServletRequest request) {
// 判斷是否已經(jīng)登陸
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() != null) {
return "你已經(jīng)登陸賬號:" + subject.getPrincipal();
}
// 獲得登陸失敗的原因
String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
// 翻譯成人類看的懂的提示
String msg = "";
if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號不存在";
} else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "密碼不正確";
} else if (LockedAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號被鎖定";
} else if (ExpiredCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號已過期";
} else {
msg = "未知";
logger.error("[login][未知登陸錯誤:{}]", shiroLoginFailure);
}
return "登陸失敗,原因:" + msg;
}
@ResponseBody
@GetMapping("/login_success")
public String loginSuccess() {
return "登陸成功";
}
@ResponseBody
@GetMapping("/unauthorized")
public String unauthorized() {
return "你沒有權(quán)限";
}
}
2.3.1 登錄頁面
GET /login 地址,跳轉(zhuǎn)登錄頁面。代碼如下:
// SecurityController.java
@GetMapping("/login")
public String loginPage() {
return "login.html";
}
- 返回
resources/static/login.html靜態(tài)頁面。代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
</head>
<body>
<form action="/login" method="post">
用戶名:<input type="text" name="username"/> <br />
密碼:<input type="password" name="password"/> <br />
<input type="submit" value="登錄"/>
</form>
</body>
</html>
- 一個簡單的登錄的表單,
POST提交登錄請求到/login地址上。
2.3.2 登錄請求
對于登錄請求,會被我們配置的 Shiro FormAuthenticationFilter 過濾器進(jìn)行攔截,進(jìn)行用戶的身份認(rèn)證。整個過程如下:
- FormAuthenticationFilter 解析請求的
username、password參數(shù),創(chuàng)建 UsernamePasswordToken 對象。 - 然后,調(diào)用 SecurityManager 的
#login(Subject subject, AuthenticationToken authenticationToken)方法,執(zhí)行登錄操作,進(jìn)行“身份驗(yàn)證”(認(rèn)證)。 - 在這內(nèi)部中,調(diào)用 Realm 的
#getAuthenticationInfo(AuthenticationToken token)方法,進(jìn)行認(rèn)證。此時,根據(jù)認(rèn)證的是否成功,會有不同的處理:- 如果認(rèn)證通過,則 FormAuthenticationFilter 會將請求重定向到
GET loginSuccess地址上。 - 【重要】如果認(rèn)證失敗,則會將認(rèn)證失敗的原因設(shè)置到請求的
attributes中,后續(xù)該請求會繼續(xù)請求到POST login地址上。這樣,在POST loginUrl地址上,我們可以從attributes中獲取到失敗的原因,提示給用戶。
- 如果認(rèn)證通過,則 FormAuthenticationFilter 會將請求重定向到
所以,POST loginUrl 的目的,實(shí)際是為了處理認(rèn)真失敗的情況。也因此,POST login 地址,實(shí)現(xiàn)代碼如下:
// SecurityController.java
@ResponseBody
@PostMapping("/login")
public String login(HttpServletRequest request) {
// <1> 判斷是否已經(jīng)登錄
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() != null) {
return "你已經(jīng)登錄賬號:" + subject.getPrincipal();
}
// <2> 獲得登錄失敗的原因
String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
// 翻譯成人類看的懂的提示
String msg = "";
if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號不存在";
} else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "密碼不正確";
} else if (LockedAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號被鎖定";
} else if (ExpiredCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "賬號已過期";
} else {
msg = "未知";
logger.error("[login][未知登錄錯誤:{}]", shiroLoginFailure);
}
return "登錄失敗,原因:" + msg;
}
-
<1>處,對于已經(jīng)登錄成功的用戶,如果我們再次請求POST loginUrl地址,依然會直接跳轉(zhuǎn)到該地址上。此處,我們是提供用戶已經(jīng)的登錄。可能會希望重新進(jìn)行一次登錄的邏輯,那么就需要重寫 FormAuthenticationFilter 過濾器。 -
<2>處,從請求的attributes中,獲取FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME對應(yīng)的值,即登錄失敗的原因。從代碼中,我們可以看出,失敗原因?yàn)楫惓5娜惷?,我們需要進(jìn)行翻譯成人類可讀的提示。
2.3.3 登錄成功
GET login_success 地址,登錄成功響應(yīng)。代碼如下:
// SecurityController.java
@ResponseBody
@GetMapping("/login_success")
public String loginSuccess() {
return "登錄成功";
}
- 如果是 AJAX 請求的情況下,我們可以返回 JSON 字符串。例如說,用戶、角色、權(quán)限等等信息。
- 如果非 AJAX 請求的情況下,重定向到登錄成功的頁面。例如說,管理后臺的 HOME 頁面。
2.3.4 未授權(quán)
GET unauthorized 地址,未授權(quán)響應(yīng)。代碼如下:
// SecurityController.java
@ResponseBody
@GetMapping("/unauthorized")
public String unauthorized() {
return "你沒有權(quán)限";
}
- 如果是 AJAX 請求的情況下,我們可以返回 JSON 字符串。例如說,你沒有權(quán)限。
- 如果非 AJAX 請求的情況下,重定向到登錄成功的頁面。例如說,未授權(quán)的頁面。
2.4 TestController
在 [controller]包路徑下,創(chuàng)建 TestController 類,提供測試 API 接口。代碼如下:
// TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/demo")
public String demo() {
return "示例返回";
}
@GetMapping("/home")
public String home() {
return "我是首頁";
}
@GetMapping("/admin")
public String admin() {
return "我是管理員";
}
@GetMapping("/normal")
public String normal() {
return "我是普通用戶";
}
}
- 對于
/test/demo接口,直接訪問,無需登錄。 - 對于
/test/home接口,無法直接訪問,需要進(jìn)行登錄。 - 對于
/test/admin接口,需要登錄「admin/admin」用戶,因?yàn)樾枰?ADMIN 角色。 - 對于
/test/normal接口,需要登錄「user/user」用戶,因?yàn)樾枰?USER 角色。
胖友可以按照如上的說明,進(jìn)行各種測試。例如說,登錄「user/user」用戶后,去訪問 /test/admin 接口,會返回?zé)o權(quán)限的提示~
2.5 Application
創(chuàng)建 Application.java 類,配置 @SpringBootApplication 注解即可。代碼如下:
// Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
至此,我們已經(jīng)完成了 Shiro 的入門。可以自己多多測試一下。
三、Shiro注解
在 Shiro 中,提供了如下五個注解,可以直接添加在 SpringMVC 的 URL 對應(yīng)的方法上,實(shí)現(xiàn)權(quán)限配置。下面,我們來分別看看。
3.1 @RequiresGuest
@RequiresGuest 注解,和 anon 等價。
3.2 @RequiresAuthentication
@RequiresAuthentication 注解,和 authc 等價。
3.3 @RequiresUser
@RequiresUser 注解,和 user 等價,要求必須登錄。
3.4 @RequiresRoles
@RequiresRoles 注解,和 roles 等價。代碼如下:
// RequiresRoles.java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
/**
* A single String role name or multiple comma-delimited role names required in order for the method
* invocation to be allowed.
*/
String[] value();
/**
* The logical operation for the permission check in case multiple roles are specified. AND is the default
* @since 1.1.0
* 當(dāng)有多個角色時,AND 表示要擁有全部角色,OR 表示擁有任一角色即可
*/
Logical logical() default Logical.AND;
}
使用示例如下:
// 屬于 NORMAL 角色
@RequiresRoles("NORMAL")
// 要同時擁有 ADMIN 和 NORMAL 角色
@RequiresRoles({"ADMIN", "NORMAL"})
// 擁有 ADMIN 或 NORMAL 任一角色即可
@RequiresRoles(value = {"ADMIN", "NORMAL"}, logical = Logical.OR)
如果驗(yàn)證權(quán)限不通過,則會拋出 AuthorizationException 異常。此時,我們可以基于 Spring MVC 提供的 @RestControllerAdvice + @ExceptionHandler 注解,實(shí)現(xiàn)全局異常的處理。不了解的胖友,可以看看《芋道 Spring Boot SpringMVC 入門》的「5. 全局異常處理」小節(jié)。
3.5 @RequiresPermissions
@RequiresPermissions 注解,和 perms 等價。代碼如下:
// RequiresPermissions.java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
/**
* The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
* to determine if the user is allowed to invoke the code protected by this annotation.
*/
String[] value();
/**
* The logical operation for the permission checks in case multiple roles are specified. AND is the default
* @since 1.1.0
* 當(dāng)有多個權(quán)限時,AND 表示要擁有全部權(quán)限,OR 表示擁有任一權(quán)限即可
*/
Logical logical() default Logical.AND;
}
使用示例如下:
// 擁有 user:add 權(quán)限
@RequiresPermissions("user:add")
// 要同時擁有 user:add 和 user:update 權(quán)限
@RequiresPermissions({"user:add", "user:update"})
// 擁有 user:add 和 user:update 任一權(quán)限即可
@RequiresPermissions(value = {"user:add", "user:update"}, logical = Logical.OR)
如果驗(yàn)證權(quán)限不通過,則會拋出 AuthorizationException 異常。此時,我們可以基于 Spring MVC 提供的 @RestControllerAdvice + @ExceptionHandler 注解,實(shí)現(xiàn)全局異常的處理。不了解的胖友,可以看看另外一篇文章的全局異常處理小節(jié)。
底線
本文源代碼使用 Apache License 2.0開源許可協(xié)議,這里是本文源碼Gitee地址,可通過命令git clone+地址下載代碼到本地,也可直接點(diǎn)擊鏈接通過瀏覽器方式查看源代碼。