0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何通過自定義注解進(jìn)行接口匿名訪問

Android編程精選 ? 來源:CSDN技術(shù)社區(qū) ? 作者:HUWD ? 2022-03-15 14:37 ? 次閱讀

在實(shí)際項(xiàng)目中使用到了springsecurity作為安全框架,我們會遇到需要放行一些接口,使其能匿名訪問的業(yè)務(wù)需求。但是每當(dāng)需要當(dāng)需要放行時,都需要在security的配置類中進(jìn)行修改,感覺非常的不優(yōu)雅。

例如這樣:

0e468b82-9475-11ec-952b-dac502259ad0.png

所以想通過自定義一個注解,來進(jìn)行接口匿名訪問。在實(shí)現(xiàn)需求前,我們先了解一下security的兩種方行思路。

第一種就是在configure(WebSecurity web)方法中配置放行,像下面這樣:

@Overridepublicvoidconfigure(WebSecurityweb)throwsException{
web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico","/verifyCode");
}

第二種方式是在configure(HttpSecurity http)方法中進(jìn)行配置:

@Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException
{
httpSecurity
.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
}

兩種方式最大的區(qū)別在于,第一種方式是不走 Spring Security 過濾器鏈,而第二種方式走 Spring Security 過濾器鏈,在過濾器鏈中,給請求放行。

在我們使用 Spring Security 的時候,有的資源可以使用第一種方式額外放行,不需要驗(yàn)證,例如前端頁面的靜態(tài)資源,就可以按照第一種方式配置放行。

有的資源放行,則必須使用第二種方式,例如登錄接口。大家知道,登錄接口也是必須要暴露出來的,不需要登錄就能訪問到的,但是我們卻不能將登錄接口用第一種方式暴露出來,登錄請求必須要走 Spring Security 過濾器鏈,因?yàn)樵谶@個過程中,還有其他事情要做,具體的登錄流程想了解的可以自行百度。

了解完了security的兩種放行策略后,我們開始實(shí)現(xiàn)

首先創(chuàng)建一個自定義注解

@Target({ElementType.METHOD})//注解放置的目標(biāo)位置,METHOD是可注解在方法級別上@Retention(RetentionPolicy.RUNTIME)//注解在哪個階段執(zhí)行@Documented//生成文檔public@interfaceIgnoreAuth{
}

這里說明一下,@Target({ElementType.METHOD})我的實(shí)現(xiàn)方式,注解只能標(biāo)記在帶有@RequestMapping注解的方法上。具體為什么下面的實(shí)現(xiàn)方式看完就懂了。

接下來創(chuàng)建一個security的配置類SecurityConfig并繼承WebSecurityConfigurerAdapter

@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter
{

@Autowired
privateRequestMappingHandlerMappingrequestMappingHandlerMapping;

/**
*@description:使用這種方式放行的接口,不走SpringSecurity過濾器鏈,
*無法通過SecurityContextHolder獲取到登錄用戶信息的,
*因?yàn)樗婚_始沒經(jīng)過 SecurityContextPersistenceFilter 過濾器鏈。
*@dateTime:2021/7/1910:22
*/
@Override
publicvoidconfigure(WebSecurityweb)throwsException{
WebSecurityand=web.ignoring().and();
MaphandlerMethods=requestMappingHandlerMapping.getHandlerMethods();
handlerMethods.forEach((info,method)->{
//帶IgnoreAuth注解的方法直接放行
if(StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))){
//根據(jù)請求類型做不同的處理
info.getMethodsCondition().getMethods().forEach(requestMethod->{
switch(requestMethod){
caseGET:
//getPatternsCondition得到請求url數(shù)組,遍歷處理
info.getPatternsCondition().getPatterns().forEach(pattern->{
//放行
and.ignoring().antMatchers(HttpMethod.GET,pattern);
});
break;
casePOST:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.POST,pattern);
});
break;
caseDELETE:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.DELETE,pattern);
});
break;
casePUT:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.PUT,pattern);
});
break;
default:
break;
}
});
}
});
}
}

在這里使用Spring為我們提供的RequestMappingHandlerMapping類,我們可以通過requestMappingHandlerMapping.getHandlerMethods();獲取到所有的RequestMappingInfo信息。

以下是源碼部分,可不看,看了可以加深理解

這里簡單說一下RequestMappingHandlerMapping的工作流程,便于理解。我們通過翻看源碼

0e5acca0-9475-11ec-952b-dac502259ad0.png

繼承關(guān)系如上圖所示。

AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口

publicinterfaceInitializingBean{
voidafterPropertiesSet()throwsException;
}

AbstractHandlerMethodMapping類中通過afterPropertiesSet方法調(diào)用initHandlerMethods進(jìn)行初始化

publicvoidafterPropertiesSet(){
this.initHandlerMethods();
}

protectedvoidinitHandlerMethods(){
String[]var1=this.getCandidateBeanNames();
intvar2=var1.length;

for(intvar3=0;var3if(!beanName.startsWith("scopedTarget.")){
this.processCandidateBean(beanName);
}
}

this.handlerMethodsInitialized(this.getHandlerMethods());
}

再調(diào)用processCandidateBean方法:

protectedvoidprocessCandidateBean(StringbeanName){
ClassbeanType=null;

try{
beanType=this.obtainApplicationContext().getType(beanName);
}catch(Throwablevar4){
if(this.logger.isTraceEnabled()){
this.logger.trace("Couldnotresolvetypeforbean'"+beanName+"'",var4);
}
}

if(beanType!=null&&this.isHandler(beanType)){
this.detectHandlerMethods(beanName);
}

}

通過調(diào)用方法中的isHandler方法是不是requestHandler方法,可以看到源碼是通過RequestMapping,Controller 注解進(jìn)行判斷的。

protectedbooleanisHandler(ClassbeanType){
returnAnnotatedElementUtils.hasAnnotation(beanType,Controller.class)||AnnotatedElementUtils.hasAnnotation(beanType,RequestMapping.class);
}

判斷通過后,調(diào)用detectHandlerMethods方法將handler注冊到HandlerMethod的緩存中。

protectedvoiddetectHandlerMethods(Objecthandler){
ClasshandlerType=handlerinstanceofString?this.obtainApplicationContext().getType((String)handler):handler.getClass();
if(handlerType!=null){
ClassuserType=ClassUtils.getUserClass(handlerType);
Mapmethods=MethodIntrospector.selectMethods(userType,(method)->{
try{
returnthis.getMappingForMethod(method,userType);
}catch(Throwablevar4){
thrownewIllegalStateException("Invalidmappingonhandlerclass["+userType.getName()+"]:"+method,var4);
}
});
if(this.logger.isTraceEnabled()){
this.logger.trace(this.formatMappings(userType,methods));
}

methods.forEach((method,mapping)->{
MethodinvocableMethod=AopUtils.selectInvocableMethod(method,userType);
this.registerHandlerMethod(handler,invocableMethod,mapping);
});
}

}

通過registerHandlerMethod方法將handler放到private final Map mappingLookup = new LinkedHashMap();map中。

requestMappingHandlerMapping.getHandlerMethods()方法就是獲取所有的HandlerMapping。

publicMapgetHandlerMethods(){
this.mappingRegistry.acquireReadLock();

Mapvar1;
try{
var1=Collections.unmodifiableMap(this.mappingRegistry.getMappings());
}finally{
this.mappingRegistry.releaseReadLock();
}

returnvar1;
}

最后就是對map進(jìn)行遍歷,判斷是否帶有IgnoreAuth.class注解,然后針對不同的請求方式進(jìn)行放行。

handlerMethods.forEach((info,method)->{
//帶IgnoreAuth注解的方法直接放行
if(StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))){
//根據(jù)請求類型做不同的處理
info.getMethodsCondition().getMethods().forEach(requestMethod->{
switch(requestMethod){
caseGET:
//getPatternsCondition得到請求url數(shù)組,遍歷處理
info.getPatternsCondition().getPatterns().forEach(pattern->{
//放行
and.ignoring().antMatchers(HttpMethod.GET,pattern);
});
break;
casePOST:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.POST,pattern);
});
break;
caseDELETE:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.DELETE,pattern);
});
break;
casePUT:
info.getPatternsCondition().getPatterns().forEach(pattern->{
and.ignoring().antMatchers(HttpMethod.PUT,pattern);
});
break;
default:
break;
}
});
}
});

看到這里就能理解我最開始的強(qiáng)調(diào)的需標(biāo)記在帶有@RequestMapping注解的方法上。我這里使用到的是configure(WebSecurity web)的放行方式。它是不走security的過濾鏈,是無法通過SecurityContextHolder獲取到登錄用戶信息的,這點(diǎn)問題是需要注意的。

審核編輯:郭婷


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8596

    瀏覽量

    151145

原文標(biāo)題:如何利用自定義注解放行 Spring Security 項(xiàng)目的接口

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Springboot是如何獲取自定義異常并進(jìn)行返回的

    這里看到新服務(wù)是封裝的自定義異常,準(zhǔn)備入手剖析一下,自定義的異常是如何進(jìn)行抓住我們請求的方法的異常,并進(jìn)行封裝返回到。廢話不多說,先看看如何才能實(shí)現(xiàn)封裝異常,先來一個示例:在這里,您會
    發(fā)表于 03-22 14:15

    1602自定義字符

    1602液晶能夠顯示自定義字符,能夠根據(jù)讀者的具體情況顯示自定義字符。
    發(fā)表于 01-20 15:43 ?1次下載

    自定義fifo接口控制器

    自定義fifo接口控制器,利用sopc builder實(shí)現(xiàn)。
    發(fā)表于 03-22 14:09 ?1次下載

    使用Spring自定義注解的實(shí)現(xiàn)

    執(zhí)行器。其本質(zhì)就是通過外部參數(shù)進(jìn)行一次路由和Spring mvc做的事情類似。簡單看了Spring mvc的實(shí)現(xiàn)原理之后,決定使用自定義注解的方式來實(shí)現(xiàn)以上功能。
    發(fā)表于 09-28 11:55 ?0次下載

    AN958:自定義設(shè)計(jì)的調(diào)試和編程接口

    包含調(diào)試和編程接口連接器??赡艿倪x項(xiàng)有全面支持STK的所有調(diào)試和編程功能,僅限串行線編程。本應(yīng)用說明介紹了在自定義硬件設(shè)計(jì)中包括這些連接器接口的優(yōu)點(diǎn),并提供了有關(guān)這些接口的詳細(xì)信息。
    發(fā)表于 02-28 15:14 ?2次下載

    自定義sobel濾波IP核,IP接口遵守AXI Stream協(xié)議

    自定義sobel濾波IP核 IP接口遵守AXI Stream協(xié)議
    的頭像 發(fā)表于 08-06 06:04 ?3919次閱讀

    如何通過LUA實(shí)現(xiàn)自定義串口指令設(shè)置

    本章節(jié)主要講述通過 LUA 實(shí)現(xiàn)自定義串口指令設(shè)置按鈕按下、設(shè)置文本、設(shè)置蜂鳴器響。并在按下按鈕或通過鍵盤輸入數(shù)據(jù)后發(fā)送自定義指令。本文將分為以下是 4 個階段講述教程 DEMO 是如
    發(fā)表于 10-17 08:00 ?8次下載
    如何<b class='flag-5'>通過</b>LUA實(shí)現(xiàn)<b class='flag-5'>自定義</b>串口指令設(shè)置

    C#與STM32自定義通信協(xié)議

    C#與STM32自定義通信協(xié)議功能:1.可通過C#上位機(jī)對多臺STM32下位機(jī)進(jìn)行控制2.自定義上位機(jī)與下位機(jī)通信協(xié)議
    發(fā)表于 12-24 18:59 ?37次下載
    C#與STM32<b class='flag-5'>自定義</b>通信協(xié)議

    自定義視圖組件教程案例

    自定義組件 1.自定義組件-particles(粒子效果) 2.自定義組件- pulse(脈沖button效果) 3.自定義組件-progress(progress效果) 4.
    發(fā)表于 04-08 10:48 ?14次下載

    ArkUI如何自定義彈窗(eTS)

    自定義彈窗其實(shí)也是比較簡單的,通過CustomDialogController類就可以顯示自定義彈窗。
    的頭像 發(fā)表于 08-31 08:24 ?2197次閱讀

    labview自定義控件

    labview自定義精美控件
    發(fā)表于 05-15 16:46 ?17次下載

    自定義算子開發(fā)

    一個完整的自定義算子應(yīng)用過程包括注冊算子、算子實(shí)現(xiàn)、含自定義算子模型轉(zhuǎn)換和運(yùn)行含自定義op模型四個階段。在大多數(shù)情況下,您的模型應(yīng)該可以通過使用hb_mapper工具完成轉(zhuǎn)換并順利部署
    的頭像 發(fā)表于 04-07 16:11 ?2811次閱讀
    <b class='flag-5'>自定義</b>算子開發(fā)

    自定義AXI-Lite接口的IP及源碼分析

    在 Vivado 中自定義 AXI4-Lite 接口的 IP,實(shí)現(xiàn)一個簡單的 LED 控制功能,并將其掛載到 AXI Interconnect 總線互聯(lián)結(jié)構(gòu)上,通過 ZYNQ 主機(jī)控制,后面對 Xilinx 提供的整個 AXI4
    發(fā)表于 06-25 16:31 ?3352次閱讀
    <b class='flag-5'>自定義</b>AXI-Lite<b class='flag-5'>接口</b>的IP及源碼分析

    labview超快自定義控件制作和普通自定義控件制作

    labview超快自定義控件制作和普通自定義控件制作
    發(fā)表于 08-21 10:32 ?13次下載

    TSMaster 自定義 LIN 調(diào)度表編程指導(dǎo)

    LIN(LocalInterconnectNetwork)協(xié)議調(diào)度表是用于LIN總線通信中的消息調(diào)度的一種機(jī)制,我們收到越來越多來自不同用戶希望能夠通過接口實(shí)現(xiàn)自定義LIN調(diào)度表的需求。所以在
    的頭像 發(fā)表于 05-11 08:21 ?673次閱讀
    TSMaster <b class='flag-5'>自定義</b> LIN 調(diào)度表編程指導(dǎo)