网易首页 > 网易号 > 正文 申请入驻

【258期】如何利用自定义注解放行 Spring Security项目的接口?

0
分享至

点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!

下方有惊喜,留言必回,有问必答!

每一天进步一点点,是成功的开始...

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

例如这样:

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

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

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");

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

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
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 @interface IgnoreAuth {

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

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

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter

@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

/**
* @ description: 使用这种方式放行的接口,不走 Spring Security 过滤器链,
* 无法通过 SecurityContextHolder 获取到登录用户信息的,
* 因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。
* @ dateTime: 2021/7/19 10:22
*/
@Override
public void configure(WebSecurity web) throws Exception {
WebSecurity and = web.ignoring().and();
Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
handlerMethods.forEach((info, method) -> {
// 带IgnoreAuth注解的方法直接放行
if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) {
// 根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到请求url数组,遍历处理
info.getPatternsCondition().getPatterns().forEach(pattern -> {
// 放行
and.ignoring().antMatchers(HttpMethod.GET, pattern);
});
break;
case POST:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.POST, pattern);
});
break;
case DELETE:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.DELETE, pattern);
});
break;
case PUT:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.PUT, pattern);
});
break;
default:
break;
}
});
}
});
}
}

在这里使用Spring为我们提供的RequestMappingHandlerMapping类,我们可以通过requestMappingHandlerMapping.getHandlerMethods();获取到所有的RequestMappingInfo信息。

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

这里简单说一下RequestMappingHandlerMapping的工作流程,便于理解。我们通过翻看源码

继承关系如上图所示。

AbstractHandlerMethodMapping实现了InitializingBean接口

public interface InitializingBean {
void afterPropertiesSet() throws Exception;

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

public void afterPropertiesSet() {
this.initHandlerMethods();

protected void initHandlerMethods() {
String[] var1 = this.getCandidateBeanNames();
int var2 = var1.length;

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

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

再调用processCandidateBean方法:

protected void processCandidateBean(String beanName) {
Class beanType = null;

try {
beanType = this.obtainApplicationContext().getType(beanName);
} catch (Throwable var4) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
}
}

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

}

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

protected boolean isHandler(Class beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);

判断通过后,调用detectHandlerMethods方法将handler注册到HandlerMethod的缓存中。

protected void detectHandlerMethods(Object handler) {
Class handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
if (handlerType != null) {
Class userType = ClassUtils.getUserClass(handlerType);
Map methods = MethodIntrospector.selectMethods(userType, (method) -> {
try {
return this.getMappingForMethod(method, userType);
} catch (Throwable var4) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);

if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));

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

}

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

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

public Map getHandlerMethods() {
this.mappingRegistry.acquireReadLock();

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

return var1;
}

handlerMethods.forEach((info, method) -> {
// 带IgnoreAuth注解的方法直接放行
if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) {
// 根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到请求url数组,遍历处理
info.getPatternsCondition().getPatterns().forEach(pattern -> {
// 放行
and.ignoring().antMatchers(HttpMethod.GET, pattern);
break;
case POST:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.POST, pattern);
break;
case DELETE:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.DELETE, pattern);
break;
case PUT:
info.getPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.PUT, pattern);
break;
default:
break;


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

版权声明:本文为CSDN博主「HUWD」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 https://blog.csdn.net/weixin_45089791/article/details/118890274

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

------ THE END ------

精品资料,超赞福利!


3000+ 道面试题在线刷,最新、最全 Java 面试题!

期往精选 点击标题可跳转

技术交流群!

最近有很多人问,有没有读者交流群!想知道如何加入?方式很简单,兴趣相投的朋友,只需要点击下方卡片,回复“加群”,即可无套路入交流群!

文章有帮助的话,在看,转发吧!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
有人饿死就开战?菲律宾司令放狠话威胁,中国会撤出仁爱礁?

有人饿死就开战?菲律宾司令放狠话威胁,中国会撤出仁爱礁?

就像当初啊
2026-06-13 09:54:36
制裁菲防长当日!日本彻底慌了:火速喊话俄罗斯,从中国北边开始

制裁菲防长当日!日本彻底慌了:火速喊话俄罗斯,从中国北边开始

白日追梦人
2026-06-13 14:23:11
虎扑网友自曝婚前过往:破过三个c

虎扑网友自曝婚前过往:破过三个c

自愈小日子
2026-06-12 01:03:28
SpaceX首秀大涨,散户挤爆券商系统,批量制造400个亿万富翁,公司总裁:他们依然坚守岗位!马斯克:曾以为成功率不足10%,将带你们去火星

SpaceX首秀大涨,散户挤爆券商系统,批量制造400个亿万富翁,公司总裁:他们依然坚守岗位!马斯克:曾以为成功率不足10%,将带你们去火星

每日经济新闻
2026-06-13 11:05:22
口交、肛交等进入式性服务是卖淫行为吗?最高院定调了!

口交、肛交等进入式性服务是卖淫行为吗?最高院定调了!

黯泉
2026-06-02 11:54:54
为了杀印度人专门设置外卖陷阱?美国对印度人仇视愈演愈烈!

为了杀印度人专门设置外卖陷阱?美国对印度人仇视愈演愈烈!

花小猫的美食日常
2026-06-13 03:08:52
丁克22年丈夫猝逝,她守着旧房不搬家:女人最后悔的,不是没孩子

丁克22年丈夫猝逝,她守着旧房不搬家:女人最后悔的,不是没孩子

飘飘然的娱乐汇
2026-06-12 19:30:08
4年1.785亿顶薪,好几支球队疯抢,詹姆斯这下高兴了

4年1.785亿顶薪,好几支球队疯抢,詹姆斯这下高兴了

从零到一研究所
2026-06-13 12:54:31
安徽入室抢劫案,母亲被嫌疑人蹂躏三小时,死前哀求他别吵醒女儿

安徽入室抢劫案,母亲被嫌疑人蹂躏三小时,死前哀求他别吵醒女儿

历史品鉴仓
2026-06-11 17:19:50
浙江省委副秘书长陈衡治,履新职!赵聪,已任文旅部公共服务司司长!

浙江省委副秘书长陈衡治,履新职!赵聪,已任文旅部公共服务司司长!

叮当当科技
2026-06-13 14:18:23
官方:斯诺克三大赛将不再设立两杆147奖金、单赛季百杆破百奖金

官方:斯诺克三大赛将不再设立两杆147奖金、单赛季百杆破百奖金

懂球帝
2026-06-13 01:18:15
执法行动刚结束,大陆海警船进入太平岛,国台办对民进党称呼变了

执法行动刚结束,大陆海警船进入太平岛,国台办对民进党称呼变了

80后房车生活
2026-06-13 11:41:44
绍兴“小巴厘岛”爆火出圈!沪杭游客专程赶来打卡

绍兴“小巴厘岛”爆火出圈!沪杭游客专程赶来打卡

绍兴E网
2026-06-13 13:00:40
世界杯开赛!澳大利亚队是亚洲的?英国有11个队?这些冷知识你知道吗?

世界杯开赛!澳大利亚队是亚洲的?英国有11个队?这些冷知识你知道吗?

成都商报教育报道
2026-06-12 16:49:35
台军称歼七成解放军,登陆战将发射百万火箭弹

台军称歼七成解放军,登陆战将发射百万火箭弹

赫埰足球解说
2026-06-13 10:45:32
日本天皇对高市早苗的不满,已经到了差点“发飙”的地步了

日本天皇对高市早苗的不满,已经到了差点“发飙”的地步了

靓仔情感
2026-06-13 14:09:52
印度军机坠毁

印度军机坠毁

上观新闻
2026-06-13 14:59:00
创业失败负债5亿,日本男子绑26个气球欲飞往美国,至今下落不明

创业失败负债5亿,日本男子绑26个气球欲飞往美国,至今下落不明

怪罗
2026-06-12 17:36:07
布达拉宫地下世界复杂得吓人!
金碧辉煌下藏着1200多个“地垄”

布达拉宫地下世界复杂得吓人! 金碧辉煌下藏着1200多个“地垄”

西楼知趣杂谈
2026-06-12 08:54:44
如今来看,马斯克"安插"在中国的秘密武器,已经见效了!

如今来看,马斯克"安插"在中国的秘密武器,已经见效了!

无情有思可
2026-06-13 06:07:35
2026-06-13 15:27:00
Java精选
Java精选
一场永远也演不完的戏
1794文章数 3859关注度
往期回顾 全部

科技要闻

SpaceX上市首日破2万亿美元,马斯克再封神

头条要闻

专家:中国制裁外国防长及其亲属极为少见 是杀鸡儆猴

头条要闻

专家:中国制裁外国防长及其亲属极为少见 是杀鸡儆猴

体育要闻

东道主三战不败!美墨开门红加拿大零的突破

娱乐要闻

12年情怀碎一地!跑男接连翻车

财经要闻

梁文锋向左,杨植麟向右

汽车要闻

2026重庆车展 长城炮Hi4-T正式上市售14.98万起

态度原创

教育
房产
时尚
艺术
军事航空

教育要闻

已知ABCDEF✖️F=999999,求ABCDEF各等于多少?

房产要闻

海南最赚钱行业曝光!最快4年半,海口全款买三房!

让女明星排队道歉,是内娱的耻辱

艺术要闻

书法各体临习方法

军事要闻

伊外长披露伊美谅解备忘录草案部分内容

无障碍浏览 进入关怀版