SpringSecurity自定义注解忽略鉴权允许用户匿名访问接口:解决需要多次修改配置文件问题


霜降
原创
发布时间: 2025-09-20 16:03:15 | 阅读数 0收藏数 0评论数 0
封面
在基于 Spring Security 的项目开发中,通常所有接口都会经过统一的安全拦截与权限校验。但在实际业务中,往往存在一些接口需要允许匿名访问,比如登录、注册、验证码、健康检查等。为了避免在配置文件或过滤器中反复手动排除这些接口,我们可以通过 自定义注解 的方式,优雅地标记需要忽略鉴权的接口,并在安全过滤逻辑中进行统一处理。本文将介绍如何在 Spring Security 中实现自定义注解来忽略鉴权,从而让特定接口支持匿名访问,提升代码的灵活性与可维护性。
1

demo初始效果

  1. 如图所示我写了一个security的小demo 我们正常的security 访问接口都需要登录才能访问 就像图2 图3的效果
  2. 那么如果我们想要这个接口不登录也能访问该怎么办呢 看第二步
2

配置允许匿名用户访问

  1. 如图1所示在config包里面新建一个类叫SecurityConfig
  2. 然后内容配置如下,配置好这个配置之后效果如图2所示 就不需要登录可以直接访问了


/**
* spring security 的配置类
*
* @author GJQ
* @date 2025/6/8 14:36
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
// 放行 /t // 允许匿名用户访问
.requestMatchers("/t").permitAll()
// 除了上面的所有请求都需要认证
.anyRequest().authenticated())
// 开启默认登录页
.formLogin(Customizer.withDefaults())
// 开启默认注销功能
.logout(Customizer.withDefaults());

return http.build();
}

/**
* 强散列哈希加密实现
*/
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}


3

发现问题

  1. 但是这样出现一个问题就是我们每添加一个接口如果需要进行配置那么就需要更改config的内容 到时候如果接口多了config就会很乱
  2. 举个例子如图所示
  3. 所以我想的是只要在每个接口上面添加上我们写的注解他就自动添加进入到config中去
4

注解-声明元注解

创建一个元注解的类 如图所示 具体内容如下


package com.doit.securitydemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 匿名许可注解,放在controller上面可以自动放到permitAll中
* 允许用户匿名访问
* @author GJQ
* @date 2023/8/29 14:48
*/
@Retention(RetentionPolicy.RUNTIME) //生命周期:在运行时有效 运行java时jvm会保留注解
@Target(ElementType.METHOD) //用于指定该注解可以修饰那些元素 (方法)
public @interface AnonymousPermit {
}


元注解 - > Annotation(注解)

  1. 从JDK 1.5开始, Java增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。
  2. 注解其实就是代码里的特殊标记,它用于替代配置文件:传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
  3. 注解可以标记在包、类、属性、方法,方法参数以及局部变量上,且同一个地方可以同时标记多个注解。

简单来讲元注解其实就起到了一个标注的作用,从而让我们的开发能顺利的找到


@Retention:指定其所修饰的注解的保留策略
@Document:该注解是一个标记注解,用于指示一个注解将被文档化
@Target:用来限制注解的使用范围
@Inherited:该注解使父类的注解能被其子类继承
@Repeatable:该注解是Java8新增的注解,用于开发重复注解
类型注解(Type Annotation):该注解是Java8新增的注解,可以用在任何用到类型的地方
5

注解-获取接口地址

  1. 然后我们在接口上方添加上我们刚刚声明好的注解 如图1所示
  2. 创建一个service 接口 具体内容如下 如图2所示
package com.doit.securitydemo.service;

import org.springframework.http.HttpMethod;

import java.util.Map;
import java.util.Set;

/**
* 注解 AnonymousPermit的实现接口
*
* @author GJQ
* @date 2023/8/30 8:53
*/
public interface AnonymousPermitService {

/**
* 获取匿名不登录即可以使用的方法
*
* @return Map<请求类型,路径地址>
*/
Map<HttpMethod, Set<String>> getAnonymousPermitMethodMapping();
}


3. 编写该接口的实现 内容如下 如图3所示

package com.doit.securitydemo.service.impl;

import com.doit.securitydemo.annotation.AnonymousPermit;
import com.doit.securitydemo.service.AnonymousPermitService;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* 注解 AnonymousPermit的实现类
*
* @author GJQ
* @date 2023/8/30 8:58
*/
@Service
public class AnonymousPermitServiceImpl implements AnonymousPermitService {

@Resource
private ApplicationContext applicationContext;

/**
* 获取匿名不登录即可以使用的方法
*
* @return Map<请求类型, 路径地址>
*/
@Override
public Map<HttpMethod, Set<String>> getAnonymousPermitMethodMapping() {
//创建一个key是请求类型 set为路径的map集合 用于存放最后的结果返回
Map<HttpMethod, Set<String>> requestMap = new HashMap<>();
// 创建不同请求方式的初始值
// GET
requestMap.put(HttpMethod.GET, new HashSet<>());
// POST
requestMap.put(HttpMethod.POST, new HashSet<>());
// PUT
requestMap.put(HttpMethod.PUT, new HashSet<>());
// DELETE
requestMap.put(HttpMethod.DELETE, new HashSet<>());
// OPTIONS
requestMap.put(HttpMethod.OPTIONS, new HashSet<>());
// HEAD
requestMap.put(HttpMethod.HEAD, new HashSet<>());
// TRACE
requestMap.put(HttpMethod.TRACE, new HashSet<>());
// null是所有请求 RequestMapping
requestMap.put(null, new HashSet<>());
// 通过getBean获取RequestMappingHandlerMapping对象
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
// 获取所有类中被@RequestMapping标注过的方法的对象(Method对象)
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 循环handlerMethodMap
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoHandlerMethodEntry : handlerMethodMap.entrySet()) {
// 判断方法上面是否被AnonymousPermit注解标记
if (markedAnonymousPermit(infoHandlerMethodEntry)) {
//根据不同的类型遍历存储路径的值
forEachSetRequestMethod(requestMap, infoHandlerMethodEntry);
}
}
// 返回结果
return requestMap;
}

/**
* 判断方法上面是否被标记AnonymousPermit注解
*
* @param infoHandlerMethodEntry 单个的方法对象
* @return true/false
*/
private Boolean markedAnonymousPermit(Map.Entry<RequestMappingInfo, HandlerMethod> infoHandlerMethodEntry) {
// 获取请求方法的详细信息
HandlerMethod handlerMethod = infoHandlerMethodEntry.getValue();
// 判断控制器方法是否被AnonymousPermit注解修饰
AnonymousPermit methodAnnotation = handlerMethod.getMethodAnnotation(AnonymousPermit.class);
// 不为null则被标记 反之没被标记
return methodAnnotation != null;
}

/**
* 根据不同的类型遍历存储 路径的值
*
* @param requestMap 匿名返回最后的结果集合
* @param infoHandlerMethodEntry 单个的方法对象
*/
private void forEachSetRequestMethod(Map<HttpMethod, Set<String>> requestMap,
Map.Entry<RequestMappingInfo, HandlerMethod> infoHandlerMethodEntry) {
// 获取方法的请求类型集合
Set<RequestMethod> methods = infoHandlerMethodEntry.getKey().getMethodsCondition().getMethods();
// 断言路径不能为null
assert infoHandlerMethodEntry.getKey().getPatternsCondition() != null;
// 获取方法请求的路径集合 例如 system/user/login
Set<String> patterns = infoHandlerMethodEntry.getKey().getPatternsCondition().getPatterns();
// 如果 请求类型集合没有值那么使用的就说RequestMapping 全部类型
if (methods.isEmpty()) {
requestMap.get(null).addAll(patterns);
}
// 遍历请求的方法类型集合 通过不同的类型存到不同的key中去
for (RequestMethod method : methods) {
// 根据不同的请求类型存到不同的key中
switch (method) {
case GET:
requestMap.get(HttpMethod.GET).addAll(patterns);
break;
case POST:
requestMap.get(HttpMethod.POST).addAll(patterns);
break;
case PUT:
requestMap.get(HttpMethod.PUT).addAll(patterns);
break;
case PATCH:
requestMap.get(HttpMethod.PATCH).addAll(patterns);
break;
case HEAD:
requestMap.get(HttpMethod.HEAD).addAll(patterns);
break;
case OPTIONS:
requestMap.get(HttpMethod.OPTIONS).addAll(patterns);
break;
case TRACE:
requestMap.get(HttpMethod.TRACE).addAll(patterns);
break;
case DELETE:
requestMap.get(HttpMethod.DELETE).addAll(patterns);
break;
default:
System.err.println("异常处理");
break;
}
}
}
}


4. 然后我们打印一下查看一下能不能获取到该接口的地址信息 如图4所示 如果报错那么看下一步



6

注解-报错处理

Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0: Error creating bean with name 'filterChain' defined in class path resource [com/doit/securitydemo/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' threw exception with message: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because the return value of "org.springframework.web.servlet.mvc.method.RequestMappingInfo.getPatternsCondition()" is null


如果他如图所示报错这个 那么大概意思就是 Spring BootSpring Security 的版本号不符合 要不就升级要是不能升级 那么就在配置里面添加 如下内容 如图2所示就解决了

spring.mvc.pathmatch.matching-strategy=ant_path_matcher


7

注解-完善配置

在配置更改为如下代码


@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 开启默认登录页
http.formLogin(Customizer.withDefaults());
// 开启默认注销功能
http.logout(Customizer.withDefaults());
// 查询运行匿名访问的接口
Map<HttpMethod, Set<String>> map = anonymousPermitService.getAnonymousPermitMethodMapping();
// 循环便利所有的key(key就是所有的请求类型分类) 根据分类来放行接口路径
for (HttpMethod requestMethod : map.keySet()) {
http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.requestMatchers(requestMethod, // 请求方式
map.get(requestMethod).toArray(new String[0])).permitAll());
}
// 除了上面的所有请求都需要认证
http.authorizeHttpRequests(auth->auth.anyRequest().authenticated());
return http.build();
}


8

注解-效果演示

效果看视频

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载