Spring Security 6.x 多链 + 自定义登录端点 配置说明
Spring Security 6.x 多链 + 自定义登录端点 配置说明
本文档基于你提供的完整配置代码,逐块拆解其设计思想、执行流程及扩展方式,帮助你快速看懂或二次开发。
1 整体架构速览
| 层级 | 组件 | 职责 |
| ------------------- | ------------------------------------------ | ------------------------ |
| 多 FilterChain | loginFilterChain + filterChain | 不同 URL 走不同安全配置 |
| 自定义过滤器 | UsernameAuthenticationFilter 等 | 把 HTTP 请求 → 认证对象 |
| 自定义 Provider | UsernameAuthenticationProvider 等 | 真正做“查库+比密码” |
| 结果处理器 | LoginSuccessHandler / LoginFailHandler | 登录成功/失败后返回 JSON |
| 兜底链 | filterChain (@Order(Integer.MAX_VALUE)) | 其余请求的通用安全规则 |
2 核心配置拆解
2.1 通用 HTTP 设置 commonHttpSetting
http
.formLogin(AbstractHttpConfigurer::disable) // 关闭默认 /login
.httpBasic(AbstractHttpConfigurer::disable)
.logout(AbstractHttpConfigurer::disable)
.sessionManagement(AbstractHttpConfigurer::disable) // 无状态 JWT
.csrf(AbstractHttpConfigurer::disable)
.requestCache(cache -> cache.requestCache(new HttpSessionRequestCache()))
.anonymous(AbstractHttpConfigurer::disable)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(authenticationExceptionHandler)
.accessDeniedHandler(authorizationExceptionHandler)
)
.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);
说明:
把 Spring Security 默认提供的登录、注销、Session、CSRF 等全部关掉,完全由自己控制。
2.2 登录链 loginFilterChain
@Bean
public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
commonHttpSetting(http);
http.securityMatcher("/user/login/*") // 只匹配 /user/login/ 下所有子路径
.authorizeHttpRequests(a -> a.anyRequest().authenticated());
UsernameAuthenticationFilter usernameFilter = new UsernameAuthenticationFilter(
new AntPathRequestMatcher("/user/login/username", HttpMethod.POST.name()),
new ProviderManager(List.of(applicationContext.getBean(UsernameAuthenticationProvider.class))),
loginSuccessHandler,
loginFailHandler
);
http.addFilterBefore(usernameFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
说明:
- 每个登录方式(用户名、短信、Gitee…)都对应一个
Filter+Provider。
- 新增登录方式时,只需再写一个
Filter与Provider,然后在此链里addFilterBefore即可。
2.3 兜底链 filterChain
@Bean
@Order(Integer.MAX_VALUE)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
commonHttpSetting(http);
http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.addFilterBefore(new TenantFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
说明:
@Order(Integer.MAX_VALUE)保证它是最后匹配的链。
- 负责所有非登录 URL的通用规则(认证、鉴权、Tenant 过滤器等)。
3 关键 Bean 速查
| Bean | 作用 |
| ------------------------------------------ | --------------------------------------------- |
| PasswordEncoder | 统一使用 BCryptPasswordEncoder |
| UsernameAuthenticationProvider | 处理用户名 + 密码认证 |
| SmsAuthenticationProvider | 处理短信验证码认证(已注释掉,可按需开启) |
| GiteeAuthenticationProvider | 处理 Gitee OAuth 登录(已注释掉,可按需开启) |
| LoginSuccessHandler / LoginFailHandler | 登录成功 / 失败后的 JSON 响应 |
4 如何新增一种登录方式?
-
实现
Authentication令牌类(如SmsCodeAuthenticationToken)。 -
实现
AbstractAuthenticationProcessingFilter(如SmsAuthenticationFilter)。 -
实现
AuthenticationProvider(如SmsAuthenticationProvider)。 -
在
loginFilterChain里再加一段:
SmsAuthenticationFilter smsFilter = new SmsAuthenticationFilter(
new AntPathRequestMatcher("/user/login/sms", HttpMethod.POST.name()),
new ProviderManager(List.of(applicationContext.getBean(SmsAuthenticationProvider.class))),
loginSuccessHandler,
loginFailHandler
);
http.addFilterBefore(smsFilter, UsernamePasswordAuthenticationFilter.class);
5 一句话记忆
“多链分 URL,自定义过滤器收请求,自定义 Provider 做校验,Handler 返回结果,其余请求兜底链兜底。”
示例代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final ApplicationContext applicationContext;
public SecurityConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private final AuthenticationEntryPoint authenticationExceptionHandler = new CustomAuthenticationExceptionHandler();
private final AccessDeniedHandler authorizationExceptionHandler = new CustomAuthorizationExceptionHandler();
private final Filter globalSpringSecurityExceptionHandler = new CustomSecurityExceptionHandler();
private void commonHttpSetting(HttpSecurity http) throws Exception {
// 禁用表单登录功能,移除相关的认证filter
http.formLogin(AbstractHttpConfigurer::disable)
// 禁用HTTP基本认证,移除相关的认证filter
.httpBasic(AbstractHttpConfigurer::disable)
// 禁用注销功能,移除相关的注销filter
.logout(AbstractHttpConfigurer::disable)
// 禁用会话管理功能,移除相关的会话管理filter
.sessionManagement(AbstractHttpConfigurer::disable)
// 禁用CSRF保护,移除相关的CSRF过滤器
.csrf(AbstractHttpConfigurer::disable)
// 配置请求缓存
.requestCache(cache -> cache
.requestCache(new HttpSessionRequestCache()) // 使用HttpSessionRequestCache来缓存请求
)
// 禁用匿名认证,移除相关的匿名认证filter
.anonymous(AbstractHttpConfigurer::disable);
// 处理 SpringSecurity 异常响应结果。响应数据的结构,改成业务统一的JSON结构。不要框架默认的响应结构
http.exceptionHandling(exceptionHandling ->
exceptionHandling
// 认证失败异常
.authenticationEntryPoint(authenticationExceptionHandler)
// 鉴权失败异常
.accessDeniedHandler(authorizationExceptionHandler)
);
// 其他未知异常. 尽量提前加载。
http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);
}
/**
* 密码加密使用的编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/** 登录api */
@Bean
public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
// 配置通用的HTTP设置
commonHttpSetting(http);
// 使用securityMatcher限定当前配置作用的路径
http.securityMatcher("/user/login/*")
// 配置所有请求都需要身份验证
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
// 获取登录成功处理器实例
LoginSuccessHandler loginSuccessHandler = applicationContext.getBean(LoginSuccessHandler.class);
// 获取登录失败处理器实例
LoginFailHandler loginFailHandler = applicationContext.getBean(LoginFailHandler.class);
// 加一个登录方式。用户名、密码登录
// 创建用户名登录过滤器,指定登录路径和方法,以及登录成功和失败的处理
UsernameAuthenticationFilter usernameLoginFilter = new UsernameAuthenticationFilter(
new AntPathRequestMatcher("/user/login/username", HttpMethod.POST.name()),
new ProviderManager(
List.of(applicationContext.getBean(UsernameAuthenticationProvider.class))),
loginSuccessHandler,
loginFailHandler);
// 在指定过滤器之前添加用户名登录过滤器
http.addFilterBefore(usernameLoginFilter, UsernamePasswordAuthenticationFilter.class);
// // 加一个登录方式。短信验证码 登录
// SmsAuthenticationFilter smsLoginFilter = new SmsAuthenticationFilter(
// new AntPathRequestMatcher("/user/login/sms", HttpMethod.POST.name()),
// new ProviderManager(
// List.of(applicationContext.getBean(SmsAuthenticationProvider.class))),
// loginSuccessHandler,
// loginFailHandler);
// http.addFilterBefore(smsLoginFilter, UsernamePasswordAuthenticationFilter.class);
// 加一个登录方式。Gitee 登录
// GiteeAuthenticationFilter giteeFilter = new GiteeAuthenticationFilter(
// new AntPathRequestMatcher("/user/login/gitee", HttpMethod.POST.name()),
// new ProviderManager(
// List.of(applicationContext.getBean(GiteeAuthenticationProvider.class))),
// loginSuccessHandler,
// loginFailHandler);
// http.addFilterBefore(giteeFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
@Order(Integer.MAX_VALUE)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
commonHttpSetting(http);
// 配置请求授权规则
http.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated() // 所有请求都需要经过身份验证
);
// 在filter链中添加自定义的TenantFilter
// 该filter会在UsernamePasswordAuthenticationFilter之前执行
http.addFilterBefore(new TenantFilter(), UsernamePasswordAuthenticationFilter.class);
// 构建并返回自定义的SecurityFilterChain
return http.build();
}
}
本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。
萧兮的博客:https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/f9d61cef-4864-4045-b619-c568843bebe1