springboot中shiro使用自定義注解屏蔽接口鑒權(quán)實(shí)現(xiàn)
傳統(tǒng)做法
spring boot整合shiro后,如果某些接口需要屏蔽鑒權(quán)的話(比如登錄)接口,我們一般會(huì)這么做:
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("authc", new CorsAuthorizationFilter());
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/* -- 不去攔截的接口 --*/
filterChainDefinitionMap.put("/statics/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/auth/webLogin", "anon");
filterChainDefinitionMap.put("/auth/loginPage", "anon");
filterChainDefinitionMap.put("/projectTaskDefinition/list", "anon");
/*需要攔截的接口*/
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.getFilters().put("authc", new CorsAuthorizationFilter());
return shiroFilterFactoryBean;
}但是這樣做起來(lái)不是很優(yōu)雅,每次編寫(xiě)完新的不需要鑒權(quán)的方法后需要再回來(lái)改這個(gè)地方,所以我就想能不能通過(guò)接口上加注解的方式來(lái)標(biāo)識(shí)此接口是否需要屏蔽鑒權(quán)。
使用自定義注解屏蔽接口鑒權(quán)
1.首先定義一個(gè)自定義注解AnnoApi
/**
* 將此注解加到controller的方法上,即可將方法對(duì)應(yīng)的接口地址自動(dòng)添加到白名單中
* anno是anonymous的簡(jiǎn)稱
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoApi {
}因?yàn)榇俗⒔庵黄鸬綐?biāo)識(shí)作用,所以不需要成員屬性。
2.在啟動(dòng)時(shí)獲取全部的接口路徑
為了實(shí)現(xiàn)這個(gè)功能我單獨(dú)寫(xiě)了一個(gè)ApiContxt類來(lái)處理
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class ApiContext {
// 接口路徑--方法 映射表
private Map<String, Method> pathToMethodMap;
private ApplicationContext applicationContext;
/**
* 掃描全部接口,并將其完整請(qǐng)求路徑(不包含server.servlet.context-path)與方法的映射保存下來(lái)
* 此方法默認(rèn)所有打上@RequestMapping注解(或其派生注解)的類或方法都必須有至少一個(gè)訪問(wèn)路徑,留空的話會(huì)拋出異常
* @param applicationContext
*/
public ApiContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
// 獲取全部打了@RestController注解的類
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RestController.class);
pathToMethodMap = new HashMap<>(beansWithAnnotation.size());
for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
Class<?> controller = entry.getValue().getClass();
// 獲取controller上的@RequestMapping注解
RequestMapping controllerRequestMapping = controller.getAnnotation(RequestMapping.class);
if (controllerRequestMapping != null) {
Method[] controllerSubMethods = controller.getMethods();
// 遍歷controller下的所有方法,搜索所有加了@RequestMapping注解的方法
for (Method method : controllerSubMethods) {
RequestMapping methodRequestionMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (methodRequestionMapping == null) {
continue;
}
// 將controller的訪問(wèn)路徑和method的訪問(wèn)路徑進(jìn)行拼接,并保存到一個(gè)map中
for (String controllerPath : controllerRequestMapping.value()) {
if (!controllerPath.startsWith("/")) {
controllerPath = "/" + controllerPath;
}
for (String methodPath : methodRequestionMapping.value()) {
if (!methodPath.startsWith("/")) {
methodPath = "/" + methodPath;
}
// API完整的請(qǐng)求路徑
String fullPath = controllerPath + methodPath;
pathToMethodMap.put(fullPath, method);
}
}
}
}
}
}
public Map<String, Method> getPathToMethodMap() {
return pathToMethodMap;
}
}大致意思就是將所有接口路徑與對(duì)應(yīng)方法的映射保存下來(lái),供其他類使用。
細(xì)心的小伙伴可能會(huì)發(fā)現(xiàn)一個(gè)小問(wèn)題,就是我在獲取方法路徑時(shí)取得是@RequestMapping注解的值,那么如果我的方法使用的是@PostMapping或@GetMappbing的話該怎么處理?
實(shí)際上上述代碼是可以獲取@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping @RequestMapping這些注解的路徑值的,在本文最后會(huì)簡(jiǎn)單說(shuō)一下里邊的原理,現(xiàn)在暫時(shí)認(rèn)為是可以全部獲取的就可以了。
3.配置shiro時(shí)使用ApiContext提取的接口信息配合自定義注解來(lái)動(dòng)態(tài)添加白名單
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ApiContext apiContext) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 無(wú)需攔截的接口
for (Map.Entry<String, Method> entry : apiContext.getPathToMethodMap().entrySet()) {
// 判斷方法上是否存在AnnoApi注解
AnnoApi annoApi = entry.getValue().getAnnotation(AnnoApi.class);
if (annoApi != null) {
// 接口地址是比較敏感的信息,將這個(gè)打印到日志里邊不是很安全,可以考慮關(guān)掉
log.info("添加白名單接口:" + entry.getKey());
filterChainDefinitionMap.put(entry.getKey(), "anon");
}
}
// 需要攔截的接口
filterChainDefinitionMap.put("/**", "authc");
// 使用自定義攔截器
shiroFilterFactoryBean.getFilters().put("authc", new CorsAuthorizationFilter());
return shiroFilterFactoryBean;
}循環(huán)遍歷ApiContext提供的所有接口路徑,然后判斷每一個(gè)方法上是否有@AnnoApi標(biāo)識(shí),如果有的話就將其路徑添加到白名單中,大功告成!
4.使用
使用方法很簡(jiǎn)單,在需要屏蔽鑒權(quán)的方法上添加上注解就可以了

拓展內(nèi)容:關(guān)于spring中的派生注解
在上邊第二步時(shí)我提到過(guò)這樣一個(gè)問(wèn)題
細(xì)心的小伙伴可能會(huì)發(fā)現(xiàn)一個(gè)小問(wèn)題,就是我在獲取方法路徑時(shí)取得是@RequestMapping注解的值,那么如果我的方法使用的是@PostMapping或@GetMappbing的話該怎么處理?
為什么我獲取@RequestMapping可以捎帶著將@PostMapping或@GetMappbing一并獲取了呢?
簡(jiǎn)單解釋就是@PostMapping,@GetMapping等注解是@RequestMapping的派生注解。我們隨便點(diǎn)開(kāi)@PostMapping方法可以看到,這個(gè)注解上邊被打上了@RequestMapping注解。派生注解是spring框架中的一個(gè)概念,與java本身無(wú)關(guān),這里我們不去探究其原理(主要是我也不會(huì)),只知道@PostMapping與@RequestMapping實(shí)際上是有關(guān)聯(lián)的就可以了。這個(gè)地方為了好理解也可以簡(jiǎn)單的認(rèn)為@RequestMapping相當(dāng)于是@PostMapping的父注解.

而spring框架中的工具類AnnotatedElementUtils中的findMergedAnnotation()可以獲取一個(gè)方法上的某個(gè)特定注解,如果沒(méi)有的話該方法會(huì)嘗試查找已存在注解的父注解是否滿足。所以下邊這行代碼在打了@PostMapping注解的方法上也是有效的了。
RequestMapping methodRequestionMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
另外AnnotatedElementUtils.findMergedAnnotation()還對(duì)@AliasFor注解做了處理,簡(jiǎn)單說(shuō)就是你的方法上打上了@PostMapping("add"),但是你拿到的父注解@RequestMapping中是沒(méi)有“add”這個(gè)值的,@PostMapping的源碼中通過(guò)@AliasFor注解指定了映射關(guān)系(如下圖),

然后AnnotatedElementUtils.findMergedAnnotation()方法對(duì)其進(jìn)行了處理,所以我們才能在@RequestMapping中取到路徑值。
spring中還有個(gè)類似的工具方法,AnnotationUtils.findAnnotation(),也能獲取父注解,但是這個(gè)方法并沒(méi)有對(duì)@AliasFor注解做處理,所以拿到的父注解是沒(méi)有屬性值的。
```省略部分代碼
String methodRequestPath = null; // 方法路徑
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping != null) {
methodRequestPath = mapping.value()[0];
}
if (methodRequestPath == null) {
GetMapping mapping = method.getAnnotation(GetMapping.class);
if (mapping != null) {
methodRequestPath = mapping.value()[0];
}
}
if (methodRequestPath == null) {
PostMapping mapping = method.getAnnotation(PostMapping.class);
if (mapping != null) {
methodRequestPath = mapping.value()[0];
}
}
if (methodRequestPath == null) {
PutMapping mapping = method.getAnnotation(PutMapping.class);
if (mapping != null) {
methodRequestPath = mapping.value()[0];
}
}
if (methodRequestPath == null) {
DeleteMapping mapping = method.getAnnotation(DeleteMapping.class);
if (mapping != null) {
methodRequestPath = mapping.value()[0];
}
}
```省略部分代碼這個(gè)獲取方法路徑的方法將每個(gè)注解都判斷了一下,然后取出路徑,顯然這個(gè)代碼看著很難受。
后邊修改為通過(guò) AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);獲取后就優(yōu)雅多了。
首先需要明確的是,java中的注解是不可以繼承的,所以spring中的派生注解應(yīng)該是對(duì)注解繼承的一個(gè)拓展。當(dāng)然以上提到的注解繼承、父注解等概念都是為了方便理解胡謅出來(lái)的,筆者并不保證其準(zhǔn)確性。
到此這篇關(guān)于springboot中shiro使用自定義注解屏蔽接口鑒權(quán)實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)springboot shiro自定義注解屏蔽接口鑒權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot中的@value取不到正確的值問(wèn)題
這篇文章主要介紹了springboot中的@value取不到正確的值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
java面試常見(jiàn)問(wèn)題---ConcurrentHashMap
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),今天給大家普及java面試常見(jiàn)問(wèn)題---ConcurrentHashMap知識(shí),一起看看吧2021-06-06
Java 8中字符串拼接新姿勢(shì)StringJoiner詳解
在本篇文章里小編給大家整理了關(guān)于Java 8中字符串拼接新姿勢(shì)StringJoiner的詳解內(nèi)容,需要的朋友們參考下。2019-09-09
mybatis?一對(duì)多映射?column屬性的注意事項(xiàng)說(shuō)明
這篇文章主要介紹了mybatis?一對(duì)多映射?column屬性的注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01
完美解決springboot項(xiàng)目出現(xiàn)”java: 錯(cuò)誤: 無(wú)效的源發(fā)行版:17“問(wèn)題(圖文詳解)
這篇文章主要介紹了完美解決springboot項(xiàng)目出現(xiàn)”java: 錯(cuò)誤: 無(wú)效的源發(fā)行版:17“問(wèn)題,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
SpringBoot如何配置Controller實(shí)現(xiàn)Web請(qǐng)求處理
這篇文章主要介紹了SpringBoot如何配置Controller實(shí)現(xiàn)Web請(qǐng)求處理,文中通過(guò)圖解示例介紹的很詳細(xì),具有有一定的參考價(jià)值,需要的小伙伴可以參考一下2023-05-05
Java實(shí)現(xiàn)批量發(fā)送帶附件的郵件代碼
大家好,本篇文章主要講的是Java實(shí)現(xiàn)批量發(fā)送帶附件的郵件代碼,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2022-01-01
Java的Spring框架中bean的繼承與內(nèi)部bean的注入
這篇文章主要介紹了Java的Spring框架中bean的繼承與內(nèi)部bean的注入,Spring框架是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下2015-12-12
圖文教程教你IDEA中的Spring環(huán)境搭建+簡(jiǎn)單入門
這篇文章主要介紹了圖文教程教你IDEA中的Spring環(huán)境搭建+簡(jiǎn)單入門,Spring的環(huán)境搭建使用Maven更加方便,需要的朋友可以參考下2023-03-03

