maven依赖
<!-- security启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.2</version>
</dependency>
<!-- 依赖web环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
<version>3.3.2</version>
</dependency>
配置对@PermitAll接口放行
- 配置WebSecurityConfigurerAdapter
@AutoConfiguration
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
@EnableMethodSecurity(securedEnabled = true) // 方法级别安全性控制
public class CustomWebSecurityConfigurerAdapter{
@Resource
private ApplicationContext applicationContext;
/**
* 在 Spring Security 5.7 之后通过注册SecurityFilterChain的Bean方式替代依赖实现WebSecurityConfigurerAdapter接口的方式
* @param httpSecurity
* @return
* @throws Exception
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
// 通用配置
httpSecurity
// 开启跨域
.cors(Customizer.withDefaults())
// CSRF 禁用,因为不使用 Session
.csrf(AbstractHttpConfigurer::disable);
// 获得 @PermitAll 带来的 URL 列表,免登录
Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
httpSecurity
// ①:全局共享规则
.authorizeHttpRequests(c ->c
// 1.1 静态资源,可匿名访问
.requestMatchers(HttpMethod.GET, "/*.html", "/*.html", "/*.css", "/*.js").permitAll()
// 1.2 设置 @PermitAll 无需认证
.requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
.requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
.requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
.requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
.requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll()
.requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()
// 放行 Knife4j 和 Swagger 相关的 URL
.requestMatchers(
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/api-docs/**",
"/webjars/**",
"/doc.html"
).permitAll())
// ③:兜底规则,必须认证
.authorizeHttpRequests(c -> c.anyRequest().authenticated());
return httpSecurity.build();
}
/**
* Multimap:Google 的 Guava 库,提供.类似于 Map,但每个键可以关联多个值
*
*/
private Multimap<HttpMethod,String> getPermitAllUrlsFromAnnotations(){
Multimap<HttpMethod, String> result = HashMultimap.create();
// 获得接口对应的 HandlerMethod 集合
RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 获取所有的 HandlerMethod 映射关系
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
// 筛选出带有@PermitAll注解的接口
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
RequestMappingInfo requestMappingInfo = entry.getKey();
HandlerMethod handlerMethod = entry.getValue();
// 如果该接口没有@PermitAll注解
if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) continue;
Set<String> urls = new HashSet<>();
// 多路径的情况 @PostMapping({"/login","/hello","zef"})
if (Objects.nonNull(requestMappingInfo.getPatternsCondition()))
urls.addAll(entry.getKey().getPatternsCondition().getPatterns());
// 单路径的情况 @PostMapping("/login")
if (Objects.nonNull(requestMappingInfo.getPathPatternsCondition()))
urls.addAll(convertList(requestMappingInfo.getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
if (urls.isEmpty())continue;
// 根据请求方法,添加到 result 结果
requestMappingInfo.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
result.putAll(HttpMethod.GET, urls);
break;
case POST:
result.putAll(HttpMethod.POST, urls);
break;
case PUT:
result.putAll(HttpMethod.PUT, urls);
break;
case DELETE:
result.putAll(HttpMethod.DELETE, urls);
break;
case HEAD:
result.putAll(HttpMethod.HEAD, urls);
break;
case PATCH:
result.putAll(HttpMethod.PATCH, urls);
break;
}
});
}
return result;
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
}
- 配置
spring.factories
具体关于SpringBoot自定义Starter的实现思路
- 完成以上配置 我们可以在Controller类上添加注解将会被Spring Security放行了
@PostMapping("/login")
@PermitAll
@Operation(summary = "使用账号密码登录")
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}