在实际项目中使用到了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 项目的接口相关推荐

  1. 自定义注解+拦截器优化项目代码

    自定义注解+拦截器的优势 类似用户权限或者接口限流的需求,但并不是所有操作或者接口需要.可以使用过滤器或者拦截器,但这样就必须在配置文件里加上所有方法或者使用通配符. 所以可以采用一种比较简单灵活的方 ...

  2. 自定义注解的spring注入问题

    2019独角兽企业重金招聘Python工程师标准>>> 我就我遇到的自定义注解无法注入的问题整理一下: 1.遇到的问题 SpringMvc的注入式通过id去查找上下文,这种方式用起来 ...

  3. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  4. 利用自定义注解实现权限验证

    思路: 根据自定义注解,给对应权限能够查看到的资源的Controller方法上添加注解(也就是一个权限字符串),权限字符串跟方法是一对多的关系,同一个权限字符串可以添加到多个方法上:当用户有对应的权限 ...

  5. SpringBoot中利用自定义注解优雅地实现隐私数据脱敏(加密显示)

    前言 这两天在整改等保测出的问题,里面有一个"用户信息泄露"的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏. 数据脱敏:把系统里的一些敏感数据 ...

  6. java后端参数默认值添加枚举_利用自定义Validator和枚举类来限定接口的入参

    趁热记录下,给未来的自己 0. 前言 Spring Validation 作为一个参数验证框架,本身提供的注解已经很强大了,能覆盖大部分业务场景需求,比如:@NotNull, @NotBlank, @ ...

  7. 自定义注解和拦截器,实现接口限流防刷

    我们的目的是在指定时间内,每个用户只能进行秒杀请求指定次数. 首先,定义一个注解 写一个拦截器.就是当执行某个方法之前,将请求截获: (这里实现的只是一个思路,由于StringRedisTemplat ...

  8. 基于 Spring Security 搭建用户权限系统(二) - 自定义配置

    说明 本文的目的是如何基于 Spring Security 去扩展实现一个基本的用户权限模块, 内容会覆盖到 Spring Security 常用的配置. 文中涉及到的业务代码是不完善的, 甚至会存在 ...

  9. SpringSecurity[1]-SpringSecurity简介以及创建Spring Security第一个项目

    主要内容 Spring Security 简介 第一个Spring Security项目 UserDetailsService详解 PasswordEncoder密码解析器详解 自定义登录逻辑 自定义 ...

最新文章

  1. 拿来就能用! CTO 创业技术栈指南!
  2. Socket心跳包机制
  3. Java 技术篇 - 从指定的web网页页面中读取html内容实例演示,从http协议下的url地址中读取web页面内容方法
  4. 使用docker运行微信wechat的安装脚本
  5. 【drp 12】再识转发和重定向:SpringMVC无法跳转页面
  6. JEPaas代码_((列表)输入字段值而计算
  7. SCARA机器人与 DELTA机器人
  8. 作为一个甘肃天水人,我对罐罐茶有一种特殊的情怀
  9. 【牛客 - 368C】流星雨(概率dp,乘法逆元)
  10. 大数据 ---(4)大数据驱动的金融业务创新(用户画像-数据架构-标签建模)
  11. NTP漏洞可致Windows系统触发DoS
  12. 教你一分钟制作超炫Flash翻页相册
  13. 快手作者视频如何批量下载
  14. python 图像二值化处理
  15. java 获取拼音_Java获取汉字对应的拼音(全拼或首字母)
  16. HDU - 6606
  17. 三种求最短路算法基本描述及实现(C++)
  18. 计算机无法自动更新,电脑时间不能自动更新怎么办?
  19. CMS与三色标记算法
  20. 移动开发 html 跨平台

热门文章

  1. 在执行ab压力测试时出现socket: Too many open files (24)的解决方法
  2. SQLserver 2000 重新安装挂起的解决方法
  3. linux perl 报错 Can‘t locate CPAN.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr.... 解决方法
  4. zimbra xxe+ssrf 导致 getshell
  5. linux shell 计算时间差
  6. redis 可视化客户端
  7. linux Rootkit:x86与ARM的内联内核函数Hooking
  8. SendMessage 和 PostMessage
  9. 一个Demo让你掌握所有的android控件
  10. Android 中加载网络资源时的优化 缓存和异步机制