Spring Security 6.x 多链 + 自定义登录端点 配置说明

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
  • 新增登录方式时,只需再写一个 FilterProvider,然后在此链里 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 如何新增一种登录方式?

  1. 实现 Authentication 令牌类(如 SmsCodeAuthenticationToken)。

  2. 实现 AbstractAuthenticationProcessingFilter(如 SmsAuthenticationFilter)。

  3. 实现 AuthenticationProvider(如 SmsAuthenticationProvider)。

  4. 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