0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何通过自定义注解进行接口匿名访问

Android编程精选 来源:CSDN技术社区 作者:HUWD 2022-03-15 14:37 次阅读

在实际项目中使用到了springsecurity作为安全框架,我们会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,感觉非常的不优雅。

例如这样:

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

所以想通过自定义一个注解,来进行接口匿名访问。在实现需求前,我们先了解一下security的两种方行思路。

第一种就是在configure(WebSecurity web)方法中配置放行,像下面这样:

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

第二种方式是在configure(HttpSecurity http)方法中进行配置:

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

两种方式最大的区别在于,第一种方式是不走 Spring Security 过滤器链,而第二种方式走 Spring Security 过滤器链,在过滤器链中,给请求放行。

在我们使用 Spring Security 的时候,有的资源可以使用第一种方式额外放行,不需要验证,例如前端页面的静态资源,就可以按照第一种方式配置放行。

有的资源放行,则必须使用第二种方式,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需要登录就能访问到的,但是我们却不能将登录接口用第一种方式暴露出来,登录请求必须要走 Spring Security 过滤器链,因为在这个过程中,还有其他事情要做,具体的登录流程想了解的可以自行百度。

了解完了security的两种放行策略后,我们开始实现

首先创建一个自定义注解

@Target({ElementType.METHOD})//注解放置的目标位置,METHOD是可注解在方法级别上@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行@Documented//生成文档public@interfaceIgnoreAuth{
}

这里说明一下,@Target({ElementType.METHOD})我的实现方式,注解只能标记在带有@RequestMapping注解的方法上。具体为什么下面的实现方式看完就懂了。

接下来创建一个security的配置类SecurityConfig并继承WebSecurityConfigurerAdapter

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

@Autowired
privateRequestMappingHandlerMappingrequestMappingHandlerMapping;

/**
*@description:使用这种方式放行的接口,不走SpringSecurity过滤器链,
*无法通过SecurityContextHolder获取到登录用户信息的,
*因为它一开始没经过 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))){
//根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod->{
switch(requestMethod){
caseGET:
//getPatternsCondition得到请求url数组,遍历处理
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

继承关系如上图所示。

AbstractHandlerMethodMapping实现了InitializingBean接口

publicinterfaceInitializingBean{
voidafterPropertiesSet()throwsException;
}

AbstractHandlerMethodMapping类中通过afterPropertiesSet方法调用initHandlerMethods进行初始化

publicvoidafterPropertiesSet(){
this.initHandlerMethods();
}

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

for(intvar3=0;var3< var2; ++var3) {
            String beanName = var1[var3];
            if(!beanName.startsWith("scopedTarget.")){
this.processCandidateBean(beanName);
}
}

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

再调用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);
}

}

通过调用方法中的isHandler方法是不是requestHandler方法,可以看到源码是通过RequestMapping,Controller 注解进行判断的。

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

判断通过后,调用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进行遍历,判断是否带有IgnoreAuth.class注解,然后针对不同的请求方式进行放行。

handlerMethods.forEach((info,method)->{
//带IgnoreAuth注解的方法直接放行
if(StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))){
//根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod->{
switch(requestMethod){
caseGET:
//getPatternsCondition得到请求url数组,遍历处理
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;
}
});
}
});

看到这里就能理解我最开始的强调的需标记在带有@RequestMapping注解的方法上。我这里使用到的是configure(WebSecurity web)的放行方式。它是不走security的过滤链,是无法通过SecurityContextHolder获取到登录用户信息的,这点问题是需要注意的。

审核编辑:郭婷


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7648

    浏览量

    148521

原文标题:如何利用自定义注解放行 Spring Security 项目的接口

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    怎么访问自定义服务特征UUID

    柏树世界:自定义配置文件服务UUID可以在配置的BLE、配置文件选项卡、自定义服务窗口中查看,例如UUID:AAAAAAA-AAAAAAAAAAAA-AAAAAAAAAAAAA相关联的自定义服务特性
    发表于 09-19 07:56

    labview怎么通过自定义菜单进行界面的切换

    本帖最后由 475648141 于 2022-1-13 13:10 编辑 labview编写的上位机软件,怎么通过设计自定义菜单进行界面的切换,大佬们可有好的方法。
    发表于 11-15 16:21

    Springboot是如何获取自定义异常并进行返回的

    这里看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:在这里,您会
    发表于 03-22 14:15

    1602自定义字符

    1602液晶能够显示自定义字符,能够根据读者的具体情况显示自定义字符。
    发表于 01-20 15:43 1次下载

    自定义fifo接口控制器

    自定义fifo接口控制器,利用sopc builder实现。
    发表于 03-22 14:09 1次下载

    多FPGA系统中自定义高速串行数据接口设计

    多FPGA系统中自定义高速串行数据接口设计
    发表于 05-10 11:24 24次下载

    使用Spring自定义注解的实现

    执行器。其本质就是通过外部参数进行一次路由和Spring mvc做的事情类似。简单看了Spring mvc的实现原理之后,决定使用自定义注解的方式来实现以上功能。
    发表于 09-28 11:55 0次下载

    AN958:自定义设计的调试和编程接口

    设计中包含调试和编程接口连接器。可能的选项有全面支持STK的所有调试和编程功能,仅限串行线编程。本应用说明介绍了在自定义硬件设计中包括这些连接器接口的优点,并提供了有关这些接口的详细信
    发表于 02-28 15:14 2次下载

    如何通过LUA实现自定义串口指令设置

    本章节主要讲述通过 LUA 实现自定义串口指令设置按钮按下、设置文本、设置蜂鸣器响。并在按下按钮或通过键盘输入数据后发送自定义指令。本文将分为以下是 4 个阶段讲述教程 DEMO 是如
    发表于 10-17 08:00 8次下载
    如何<b class='flag-5'>通过</b>LUA实现<b class='flag-5'>自定义</b>串口指令设置

    C#与STM32自定义通信协议

    C#与STM32自定义通信协议功能:1.可通过C#上位机对多台STM32下位机进行控制2.自定义上位机与下位机通信协议
    发表于 12-24 18:59 36次下载
    C#与STM32<b class='flag-5'>自定义</b>通信协议

    自定义视图组件教程案例

    自定义组件 1.自定义组件-particles(粒子效果) 2.自定义组件- pulse(脉冲button效果) 3.自定义组件-progress(progress效果) 4.
    发表于 04-08 10:48 14次下载

    ArkUI如何自定义弹窗(eTS)

    自定义弹窗其实也是比较简单的,通过CustomDialogController类就可以显示自定义弹窗。
    的头像 发表于 08-31 08:24 1443次阅读

    自定义算子开发

    一个完整的自定义算子应用过程包括注册算子、算子实现、含自定义算子模型转换和运行含自定义op模型四个阶段。在大多数情况下,您的模型应该可以通过使用hb_mapper工具完成转换并顺利部署
    的头像 发表于 04-07 16:11 1914次阅读
    <b class='flag-5'>自定义</b>算子开发

    自定义AXI-Lite接口的IP及源码分析

    在 Vivado 中自定义 AXI4-Lite 接口的 IP,实现一个简单的 LED 控制功能,并将其挂载到 AXI Interconnect 总线互联结构上,通过 ZYNQ 主机控制,后面对 Xilinx 提供的整个 AXI4
    发表于 06-25 16:31 2053次阅读
    <b class='flag-5'>自定义</b>AXI-Lite<b class='flag-5'>接口</b>的IP及源码分析

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

    labview超快自定义控件制作和普通自定义控件制作
    发表于 08-21 10:32 5次下载