欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot中shiro使用自定義注解屏蔽接口鑒權(quán)實(shí)現(xiàn)

 更新時(shí)間:2022年07月07日 09:41:28   作者:傲嬌的鯉魚  
本文主要介紹了springboot中shiro使用自定義注解屏蔽接口鑒權(quán)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

傳統(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)文章

最新評(píng)論