Spring Boot 3.2 集成最新 Spring Security6 实战
引言
虽然网上已经有很多对于Spring Security6用法的最近用法的介绍,但并没有一个比较完整的示例讲解我们应该如何把以前过时的方法更新到最新的用法。因此,我以自己项目中的security配置为例,提供一个已经验证过的springSecurityFilterChain配置示例,供大伙参考。
调用链路
下面是Spring Security常见Bean在处理请求的调用链路:
/**
* 处理链路:
* login : CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthenticationManager(优先级高于 SecurityUserDetailsService) -> AuthenticationSuccessHandler/AuthenticationFailHandler
* logout: CookieToHeadersFilter -> ScSecurityContextRepository -> LogoutHandler -> LogoutSuccessHandler
* 未登录进行 url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint
* 登录后进行url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> CookieToHeadersFilter(子线程, 可以在前面ScSecurityContextRepository更新token并重新设置请求头)-> 服务接口
* 鉴权失败: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint -> ScAccessDeniedHandler
*/
各个Bean的实现并没有什么变化,不知道怎么写的可以参考这篇博客,里面的很详细:https://blog.csdn.net/yuan__once/article/details/127022613
过时的写法
在最新版的Spring Security6.x版本中,我们发现以前WebSecurityConfig的springSecurityFilterChain方法配置写法已经过时,包括我们熟悉的and()方法等,官方将在Spring Security7中移除这些过时的方法:
最新的写法
目前,官方采用Lambda的配置来替代传统的链式配置,示例如下:
可以看到,这种采用 lambda 表达式的写法能更加清晰地展示 Spring Security 在处理 url 请求的调用链路,不再像以前一样一条链路写到头,又臭又长还显得冗余。经过测试,这种写法之前过时的写法实现的调用链路是完全一致的。
(不枉我摸索半天T^T)
WebSecurityConfig完整代码参考
/**
* WebSecurityConfig 核心配置
* @Author: ZenSheep
* @Date: 2024/2/1 18:34
*/
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {
@Autowired
CookieToHeadersFilter cookieToHeadersFilter;
@Autowired
ScSecurityContextRepository scSecurityContextRepository;
@Autowired
ScAuthorizationManager scAuthorizationManager;
@Autowired
ScPermitUrlConfig scPermitUrlConfig;
@Autowired
ScAccessDeniedHandler scAccessDeniedHandler;
@Autowired
ScAuthenticationEntryPoint scAuthenticationEntryPoint;
@Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
AuthenticationFailHandler authenticationFailHandler;
@Autowired
private LogoutHandler logoutHandler;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
// /**
// *
// * 处理链路:
// * login : CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthenticationManager(优先级高于 SecurityUserDetailsService) -> AuthenticationSuccessHandler/AuthenticationFailHandler
// * logout: CookieToHeadersFilter -> ScSecurityContextRepository -> LogoutHandler -> LogoutSuccessHandler
// * 未登录进行 url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint
// * 登录后进行url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> CookieToHeadersFilter(子线程, 可以在前面ScSecurityContextRepository更新token并重新设置请求头)-> 服务接口
// * 鉴权失败: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint -> ScAccessDeniedHandler
// * @param http
// * @return
// */
// @Bean
// public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// // 将Cookie写入Http请求头中,SecurityWebFiltersOrder枚举类定义了执行次序
// http.addFilterBefore(cookieToHeadersFilter, SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
//
// http
// // 存储认证信息
// .securityContextRepository(scSecurityContextRepository)
// //请求拦截处理
// .authorizeExchange(exchange -> exchange // 请求拦截处理
// .pathMatchers(scPermitUrlConfig.permit()).permitAll() // 默认放开的地址
// .pathMatchers(HttpMethod.OPTIONS).permitAll() // 放开的请求方法
// .anyExchange().access(scAuthorizationManager) // 其它的地址走后续验证
// )
// // 登录接口
// .httpBasic()
// .and()
// .formLogin().loginPage("/api/v1/login") // 在ScAuthorizationManager的authenticate处理登录请求
// .authenticationSuccessHandler(authenticationSuccessHandler) //认证成功
// .authenticationFailureHandler(authenticationFailHandler) // 认证失败
// .and()
// .exceptionHandling().authenticationEntryPoint(scAuthenticationEntryPoint) // 未认证访问服务接口处理
// .and()
// .exceptionHandling().accessDeniedHandler(scAccessDeniedHandler) // 授权失败
// .and()
// // 登出接口
// .logout().logoutUrl("/api/v1/logout")
// .logoutHandler(logoutHandler) // 登出处理
// .logoutSuccessHandler(logoutSuccessHandler) // 登出成功处理
// .and()
// // 跨域配置
// .cors()
// .configurationSource(corsConfigurationSource())
// // 关闭csrf防护,防止用户无法被认证
// .and().csrf().disable();
//
// return http.build();
// }
/**
*
* 处理链路:
* login : CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthenticationManager(优先级高于 SecurityUserDetailsService) -> AuthenticationSuccessHandler/AuthenticationFailHandler
* logout: CookieToHeadersFilter -> ScSecurityContextRepository -> LogoutHandler -> LogoutSuccessHandler
* 未登录进行 url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint
* 登录后进行url request: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> CookieToHeadersFilter(子线程, 可以在前面ScSecurityContextRepository更新token并重新设置请求头)-> 服务接口
* 鉴权失败: CookieToHeadersFilter -> ScSecurityContextRepository -> ScAuthorizationManager -> ScAuthenticationEntryPoint -> ScAccessDeniedHandler
* @param http
* @return
*/
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// 将Cookie写入Http请求头中,SecurityWebFiltersOrder枚举类定义了执行次序
http.addFilterBefore(cookieToHeadersFilter, SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
http
// 存储认证信息
.securityContextRepository(scSecurityContextRepository)
//请求拦截处理
.authorizeExchange(exchange -> exchange // 请求拦截处理
.pathMatchers(scPermitUrlConfig.permit()).permitAll() // 默认放开的地址
.pathMatchers(HttpMethod.OPTIONS).permitAll() // 放开的请求方法
.anyExchange().access(scAuthorizationManager) // 其它的地址走后续验证
)
// 登录接口
.formLogin(form -> form
.loginPage("/api/v1/login") // 登录请求路径,在 ScAuthorizationManager 的 authenticate 处理登录请求
.authenticationSuccessHandler(authenticationSuccessHandler) // 认证成功处理
.authenticationFailureHandler(authenticationFailHandler) // 认证失败处理
)
// 处理认证/授权异常
.exceptionHandling(exceptionHandlingSpec -> exceptionHandlingSpec
.authenticationEntryPoint(scAuthenticationEntryPoint) // 未认证访问服务接口处理
.accessDeniedHandler(scAccessDeniedHandler) // 授权失败处理
)
.logout(logoutSpec -> logoutSpec
.logoutUrl("/api/v1/logout") // 登出路径
.logoutHandler(logoutHandler) // 处理登出请求
.logoutSuccessHandler(logoutSuccessHandler) // 登出成功处理
)
// 关闭csrf防护,防止用户无法被认证
.csrf(csrf -> csrf.disable())
// 跨域配置
.cors(cors -> cors.configurationSource(corsConfigurationSource()));
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许哪些网站的跨域请求
corsConfiguration.addAllowedOriginPattern("*");
// 允许所有请求方法
corsConfiguration.addAllowedMethod("*");
// 允许所有域,当请求头
corsConfiguration.addAllowedHeader("*");
// 允许携带 Authorization 头
corsConfiguration.setAllowCredentials(true);
// 允许全部请求路径
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}