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; }
但是這樣做起來不是很優(yōu)雅,每次編寫完新的不需要鑒權(quán)的方法后需要再回來改這個(gè)地方,所以我就想能不能通過接口上加注解的方式來標(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ú)寫了一個(gè)ApiContxt類來處理
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)與方法的映射保存下來 * 此方法默認(rèn)所有打上@RequestMapping注解(或其派生注解)的類或方法都必須有至少一個(gè)訪問路徑,留空的話會(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的訪問路徑和method的訪問路徑進(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)方法的映射保存下來,供其他類使用。
細(xì)心的小伙伴可能會(huì)發(fā)現(xiàn)一個(gè)小問題,就是我在獲取方法路徑時(shí)取得是@RequestMapping注解的值,那么如果我的方法使用的是@PostMapping或@GetMappbing的話該怎么處理?
實(shí)際上上述代碼是可以獲取@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping @RequestMapping這些注解的路徑值的,在本文最后會(huì)簡(jiǎn)單說一下里邊的原理,現(xiàn)在暫時(shí)認(rèn)為是可以全部獲取的就可以了。
3.配置shiro時(shí)使用ApiContext提取的接口信息配合自定義注解來動(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); // 無需攔截的接口 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í)我提到過這樣一個(gè)問題
細(xì)心的小伙伴可能會(huì)發(fā)現(xiàn)一個(gè)小問題,就是我在獲取方法路徑時(shí)取得是@RequestMapping注解的值,那么如果我的方法使用的是@PostMapping或@GetMappbing的話該怎么處理?
為什么我獲取@RequestMapping可以捎帶著將@PostMapping或@GetMappbing一并獲取了呢?
簡(jiǎn)單解釋就是@PostMapping,@GetMapping等注解是@RequestMapping的派生注解。我們隨便點(diǎn)開@PostMapping方法可以看到,這個(gè)注解上邊被打上了@RequestMapping注解。派生注解是spring框架中的一個(gè)概念,與java本身無關(guān),這里我們不去探究其原理(主要是我也不會(huì)),只知道@PostMapping與@RequestMapping實(shí)際上是有關(guān)聯(lián)的就可以了。這個(gè)地方為了好理解也可以簡(jiǎn)單的認(rèn)為@RequestMapping相當(dāng)于是@PostMapping的父注解.
而spring框架中的工具類AnnotatedElementUtils中的findMergedAnnotation()可以獲取一個(gè)方法上的某個(gè)特定注解,如果沒有的話該方法會(huì)嘗試查找已存在注解的父注解是否滿足。所以下邊這行代碼在打了@PostMapping注解的方法上也是有效的了。
RequestMapping methodRequestionMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
另外AnnotatedElementUtils.findMergedAnnotation()還對(duì)@AliasFor注解做了處理,簡(jiǎn)單說就是你的方法上打上了@PostMapping("add"),但是你拿到的父注解@RequestMapping中是沒有“add”這個(gè)值的,@PostMapping的源碼中通過@AliasFor注解指定了映射關(guān)系(如下圖),
然后AnnotatedElementUtils.findMergedAnnotation()方法對(duì)其進(jìn)行了處理,所以我們才能在@RequestMapping中取到路徑值。
spring中還有個(gè)類似的工具方法,AnnotationUtils.findAnnotation(),也能獲取父注解,但是這個(gè)方法并沒有對(duì)@AliasFor注解做處理,所以拿到的父注解是沒有屬性值的。
```省略部分代碼 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è)代碼看著很難受。
后邊修改為通過 AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);獲取后就優(yōu)雅多了。
首先需要明確的是,java中的注解是不可以繼承的,所以spring中的派生注解應(yīng)該是對(duì)注解繼承的一個(gè)拓展。當(dāng)然以上提到的注解繼承、父注解等概念都是為了方便理解胡謅出來的,筆者并不保證其準(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)文章
screw?Maven插件方式運(yùn)行時(shí)在編譯打包時(shí)跳過執(zhí)行的問題解決方法
這篇文章主要介紹了screw?Maven插件方式運(yùn)行時(shí)在編譯打包時(shí)跳過執(zhí)行的問題解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03springboot項(xiàng)目docker分層構(gòu)建的配置方式
在使用dockerfile構(gòu)建springboot項(xiàng)目時(shí),速度較慢,用時(shí)比較長(zhǎng),為了加快構(gòu)建docker鏡像的速度,采用分層構(gòu)建的方式,這篇文章主要介紹了springboot項(xiàng)目docker分層構(gòu)建,需要的朋友可以參考下2024-03-03開放封閉原則_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了開放封閉原則,開放-封閉原則是面向?qū)ο笤O(shè)計(jì)的核心所在,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Spring Boot中Elasticsearch的連接配置原理與使用詳解
在Spring Boot中,我們可以通過Elasticsearch實(shí)現(xiàn)對(duì)數(shù)據(jù)的搜索和分析,本文將介紹Spring Boot中Elasticsearch的連接配置、原理和使用方法,感興趣的可以了解一下2023-09-09使用MUI框架構(gòu)建App請(qǐng)求http接口實(shí)例代碼
下面小編就為大家分享一篇使用MUI框架構(gòu)建App請(qǐng)求http接口實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01淺談StringBuilder類的capacity()方法和length()方法的一些小坑
這篇文章主要介紹了StringBuilder類的capacity()方法和length()方法的一些小坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java實(shí)現(xiàn)文件名倒序排序的技術(shù)指南
在實(shí)際開發(fā)過程中,我們經(jīng)常需要對(duì)文件進(jìn)行操作和處理,一個(gè)常見的需求是按文件名倒序排列文件列表,以便于文件的管理和查找,本文將介紹如何在Java中實(shí)現(xiàn)文件名倒序排序,并提供詳細(xì)的代碼案例,需要的朋友可以參考下2024-08-08