如何利用自定义注解放行 Spring Security 项目的接口
在实际项目中使用到了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 Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/
在我们使用 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
{@Autowiredprivate RequestMappingHandlerMapping requestMappingHandlerMapping;/*** @ description: 使用这种方式放行的接口,不走 Spring Security 过滤器链,* 无法通过 SecurityContextHolder 获取到登录用户信息的,* 因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。* @ dateTime: 2021/7/19 10:22*/@Overridepublic void configure(WebSecurity web) throws Exception {WebSecurity and = web.ignoring().and();Map<RequestMappingInfo, HandlerMethod> 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
信息。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/
以下是源码部分,可不看,看了可以加深理解
这里简单说一下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的缓存中。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/
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<Method, T> 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<T, HandlerMethod> mappingLookup = new LinkedHashMap();
map中。
而requestMappingHandlerMapping.getHandlerMethods()
方法就是获取所有的HandlerMapping。
public Map<T, HandlerMethod> getHandlerMethods() {this.mappingRegistry.acquireReadLock();Map var1;try {var1 = Collections.unmodifiableMap(this.mappingRegistry.getMappings());} finally {this.mappingRegistry.releaseReadLock();}return var1;
}
最后就是对map进行遍历,判断是否带有IgnoreAuth.class
注解,然后针对不同的请求方式进行放行。
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
获取到登录用户信息的,这点问题是需要注意的。
来源:https://blog.csdn.net/weixin_45089791/article/details/118890274
我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!
推荐阅读
豆瓣9分以上,这7本Java经典名著,你还缺哪本?
前端抱怨 API 响应慢,怎么办?
开源前后端分离在线考试系统
··································
你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书、创过业、国企4年互联网6年。10年前毕业加入宇宙行,工资不高、也不算太忙,业余坚持研究技术和做自己想做的东西。4年后离开国企,加入永辉互联网板块的创业团队,从开发、到架构、到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。
点击阅读原文,送你免费Spring Boot教程
如何利用自定义注解放行 Spring Security 项目的接口相关推荐
- 自定义注解+拦截器优化项目代码
自定义注解+拦截器的优势 类似用户权限或者接口限流的需求,但并不是所有操作或者接口需要.可以使用过滤器或者拦截器,但这样就必须在配置文件里加上所有方法或者使用通配符. 所以可以采用一种比较简单灵活的方 ...
- 自定义注解的spring注入问题
2019独角兽企业重金招聘Python工程师标准>>> 我就我遇到的自定义注解无法注入的问题整理一下: 1.遇到的问题 SpringMvc的注入式通过id去查找上下文,这种方式用起来 ...
- ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存
基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...
- 利用自定义注解实现权限验证
思路: 根据自定义注解,给对应权限能够查看到的资源的Controller方法上添加注解(也就是一个权限字符串),权限字符串跟方法是一对多的关系,同一个权限字符串可以添加到多个方法上:当用户有对应的权限 ...
- SpringBoot中利用自定义注解优雅地实现隐私数据脱敏(加密显示)
前言 这两天在整改等保测出的问题,里面有一个"用户信息泄露"的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏. 数据脱敏:把系统里的一些敏感数据 ...
- java后端参数默认值添加枚举_利用自定义Validator和枚举类来限定接口的入参
趁热记录下,给未来的自己 0. 前言 Spring Validation 作为一个参数验证框架,本身提供的注解已经很强大了,能覆盖大部分业务场景需求,比如:@NotNull, @NotBlank, @ ...
- 自定义注解和拦截器,实现接口限流防刷
我们的目的是在指定时间内,每个用户只能进行秒杀请求指定次数. 首先,定义一个注解 写一个拦截器.就是当执行某个方法之前,将请求截获: (这里实现的只是一个思路,由于StringRedisTemplat ...
- 基于 Spring Security 搭建用户权限系统(二) - 自定义配置
说明 本文的目的是如何基于 Spring Security 去扩展实现一个基本的用户权限模块, 内容会覆盖到 Spring Security 常用的配置. 文中涉及到的业务代码是不完善的, 甚至会存在 ...
- SpringSecurity[1]-SpringSecurity简介以及创建Spring Security第一个项目
主要内容 Spring Security 简介 第一个Spring Security项目 UserDetailsService详解 PasswordEncoder密码解析器详解 自定义登录逻辑 自定义 ...
最新文章
- 拿来就能用! CTO 创业技术栈指南!
- Socket心跳包机制
- Java 技术篇 - 从指定的web网页页面中读取html内容实例演示,从http协议下的url地址中读取web页面内容方法
- 使用docker运行微信wechat的安装脚本
- 【drp 12】再识转发和重定向:SpringMVC无法跳转页面
- JEPaas代码_((列表)输入字段值而计算
- SCARA机器人与 DELTA机器人
- 作为一个甘肃天水人,我对罐罐茶有一种特殊的情怀
- 【牛客 - 368C】流星雨(概率dp,乘法逆元)
- 大数据 ---(4)大数据驱动的金融业务创新(用户画像-数据架构-标签建模)
- NTP漏洞可致Windows系统触发DoS
- 教你一分钟制作超炫Flash翻页相册
- 快手作者视频如何批量下载
- python 图像二值化处理
- java 获取拼音_Java获取汉字对应的拼音(全拼或首字母)
- HDU - 6606
- 三种求最短路算法基本描述及实现(C++)
- 计算机无法自动更新,电脑时间不能自动更新怎么办?
- CMS与三色标记算法
- 移动开发 html 跨平台
热门文章
- 在执行ab压力测试时出现socket: Too many open files (24)的解决方法
- SQLserver 2000 重新安装挂起的解决方法
- linux perl 报错 Can‘t locate CPAN.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr.... 解决方法
- zimbra xxe+ssrf 导致 getshell
- linux shell 计算时间差
- redis 可视化客户端
- linux Rootkit:x86与ARM的内联内核函数Hooking
- SendMessage 和 PostMessage
- 一个Demo让你掌握所有的android控件
- Android 中加载网络资源时的优化 缓存和异步机制