共计 8735 个字符,预计需要花费 22 分钟才能阅读完成。
在认证过程和访问授权前必须了解 spring Security 如何知道我们要求所有用户都经过身份验证?Spring Security 如何知道我们想要支持基于表单的身份验证?因此必须了解 WebSecurityConfigurerAdapter 配置类如何工作的。而且也必须了解清楚 filter 的顺序,才能更好了解其调用工作流程。
1. WebSecurityConfigurerAdapter
在使用 WebSecurityConfigurerAdapter 前,先了解 Spring security config。
Spring security config 具有三个模块,一共有 3 个 builder,认证相关的 AuthenticationManagerBuilder 和 web 相关的 WebSecurity、HttpSecurity。
- AuthenticationManagerBuilder:用来配置全局的认证相关的信息,其实就是 AuthenticationProvider 和 UserDetailsService,前者是认证服务提供商,后者是用户详情查询服务;
- WebSecurity:全局请求忽略规则配置(比如说静态文件,比如说注册页面)、全局 HttpFirewall 配置、是否 debug 配置、全局 SecurityFilterChain 配置、privilegeEvaluator、expressionHandler、securityInterceptor;
-
HttpSecurity:具体的权限控制规则配置。一个这个配置相当于 xml 配置中的一个标签。各种具体的认证机制的相关配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer 等。
WebSecurityConfigurerAdapter 提供了简洁方式来创建 WebSecurityConfigurer,其作为基类,可通过实现该类自定义配置类,主要重写这三个方法:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {} public void configure(WebSecurity web) throws Exception {} protected void configure(HttpSecurity httpSecurity) throws Exception {}
而且其自动从 SpringFactoriesLoader 查找 AbstractHttpConfigurer 让我们去扩展,想要实现必须创建一个 AbstractHttpConfigurer 的扩展类,并在 classpath 路径下创建一个文件 META-INF/spring.factories。例如:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
其源码分析:
//1.init 初始化:获取 HttpSecurity 和配置 FilterSecurityInterceptor 拦截器到 WebSecurity public void init(final WebSecurity web) throws Exception { // 获取 HttpSecurity final HttpSecurity http = getHttp(); // 配置 FilterSecurityInterceptor 拦截器到 WebSecurity web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } ...... //2. 获取 HttpSecurity 的过程 protected final HttpSecurity getHttp() throws Exception {if (http != null) {return http;} DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager(); authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // 默认的 HttpSecurity 的配置 http // 添加 CSRF 支持,使用 WebSecurityConfigurerAdapter 时,默认启用,禁用 csrf().disable() .csrf().and() // 添加 WebAsyncManagerIntegrationFilter .addFilter(new WebAsyncManagerIntegrationFilter()) // 允许配置异常处理 .exceptionHandling().and() // 将安全标头添加到响应 .headers().and() // 允许配置会话管理 .sessionManagement().and() //HttpServletRequest 之间的 SecurityContextHolder 创建 securityContext 管理 .securityContext().and() // 允许配置请求缓存 .requestCache().and() // 允许配置匿名用户 .anonymous().and() //HttpServletRequestd 的方法和属性注册在 SecurityContext 中 .servletApi().and() // 使用默认登录页面 .apply(new DefaultLoginPageConfigurer<>()).and() // 提供注销支持 .logout(); // @formatter:on ClassLoader classLoader = this.context.getClassLoader(); List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {http.apply(configurer); } } configure(http); return http; } ... //3. 可重写方法实现自定义的 HttpSecurity protected void configure(HttpSecurity http) throws Exception {logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic();} ....
从源码 init 初始化模块中的“获取 HttpSecurity”和“配置 FilterSecurityInterceptor 拦截器到 WebSecurity”中可以看出,想要 spring Security 如何知道我们要求所有用户都经过身份验证?Spring Security 如何知道我们想要支持基于表单的身份验证?只要重写 protected void configure(HttpSecurity http) throws Exception 方法即可。因此我们需要理解 HttpSecurity 的方法的作用,如何进行配置。下一节来讨论 HttpSecurity。
2. HttpSecurity
HttpSecurity 基于 Web 的安全性允许为特定的 http 请求进行配置。其有很多方法,列举一些常用的如下表:
方法 | 说明 | 使用案例 |
---|---|---|
csrf() | 添加 CSRF 支持,使用 WebSecurityConfigurerAdapter 时,默认启用 | 禁用:csrf().disable() |
openidLogin() | 用于基于 OpenId 的验证 | openidLogin().permitAll(); |
authorizeRequests() | 开启使用 HttpServletRequest 请求的访问限制 | authorizeRequests().anyRequest().authenticated() |
formLogin() | 开启表单的身份验证,如果未指定 FormLoginConfigurer#loginPage(String),则将生成默认登录页面 | formLogin().loginPage(“/authentication/login”).failureUrl(“/authentication/login?failed”) |
oauth2Login() | 开启 OAuth 2.0 或 OpenID Connect 1.0 身份验证 | authorizeRequests()..anyRequest().authenticated()..and().oauth2Login() |
rememberMe() | 开启配置“记住我”的验证 | authorizeRequests().antMatchers(“/**”).hasRole(“USER”).and().formLogin().permitAll().and().rememberMe() |
addFilter() | 添加自定义的 filter | addFilter(new CustomFilter()) |
addFilterAt() | 在指定 filter 相同位置上添加自定义 filter | addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
addFilterAfter() | 在指定 filter 位置后添加自定义 filter | addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
requestMatchers() | 开启配置 HttpSecurity,仅当 RequestMatcher 相匹配时开启 | requestMatchers().antMatchers(“/api/**”) |
antMatchers() | 其可以与 authorizeRequests()、RequestMatcher 匹配,如:requestMatchers().antMatchers(“/api/**”) | |
logout() | 添加退出登录支持。当使用 WebSecurityConfigurerAdapter 时,这将自动应用。默认情况是,访问 URL”/ logout”,使 HTTP Session 无效来清除用户,清除已配置的任何 #rememberMe() 身份验证,清除 SecurityContextHolder,然后重定向到”/login?success” | logout().deleteCookies(“remove”).invalidateHttpSession(false).logoutUrl(“/custom-logout”).logoutSuccessUrl(“/logout-success”); |
HttpSecurity 还有很多方法供我们使用,去配置 HttpSecurity。由于太多这边就不一一说明,有兴趣可去研究。
## 3. WebSecurityConfigurerAdapter 使用
WebSecurityConfigurerAdapter 示例:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
protected void configure(HttpSecurity http) throws Exception {
http
//request 设置
.authorizeRequests() //http.authorizeRequests() 方法中的自定义匹配
.antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有用户进行访问指定的 url
.antMatchers("/admin/**").hasRole("ADMIN") // 指定具有特定权限的用户才能访问特定目录,hasRole() 方法指定用户权限,且不需前缀“ROLE_“.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")//
.anyRequest().authenticated() // 任何请求没匹配的都需要进行验证
.and() //login 设置 自定义登录页面且允许所有用户登录
.formLogin()
.loginPage("/login") //The updated configuration specifies the location of the log in page 指定自定义登录页面
.permitAll(); // 允许所有用户访问登录页面. The formLogin().permitAll() 方法
.and
.logout() //logouts 设置
.logoutUrl("/my/logout") // 指定注销路径
.logoutSuccessUrl("/my/index") // 指定成功注销后跳转到指定的页面
.logoutSuccessHandler(logoutSuccessHandler) // 指定成功注销后处理类 如果使用了 logoutSuccessHandler() 的话,logoutSuccessUrl() 就会失效
.invalidateHttpSession(true) // httpSession 是否有效时间,如果使用了 SecurityContextLogoutHandler,其将被覆盖
.addLogoutHandler(logoutHandler) // 在最后增加默认的注销处理类 LogoutHandler
.deleteCookies(cookieNamesToClear);// 指定注销成功后 remove cookies
// 增加在 FilterSecurityInterceptor 前添加自定义的 myFilterSecurityInterceptor
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
NOTE:此示例只供参考
4. filter 顺序
Spring Security filter 顺序:
Filter Class | 说明 |
---|---|
ChannelProcessingFilter | 访问协议控制过滤器,可能会将我们重新定向到另外一种协议, 从 http 转换成 https |
SecurityContextPersistenceFilter | 创建 SecurityContext 安全上下文信息和 request 结束时清空 SecurityContextHolder |
ConcurrentSessionFilter | 并发访问控制过滤器, 主要功能:SessionRegistry 中获取 SessionInformation 来判断 session 是否过期,从而实现并发访问控制。 |
HeaderWriterFilter | 给 http response 添加一些 Header |
CsrfFilter | 跨域过滤器,跨站请求伪造保护 Filter |
LogoutFilter | 处理退出登录的 Filter |
X509AuthenticationFilter | 添加 X509 预授权处理机制支持 |
CasAuthenticationFilter | 认证 filter,经过这些过滤器后 SecurityContextHolder 中将包含一个完全组装好的 Authentication 对象,从而使后续鉴权能正常执行 |
UsernamePasswordAuthenticationFilter | 认证的 filter,经过这些过滤器后 SecurityContextHolder 中将包含一个完全组装好的 Authentication 对象,从而使后续鉴权能正常执行。表单认证是最常用的一个认证方式。 |
BasicAuthenticationFilter | 认证 filter,经过这些过滤器后 SecurityContextHolder 中将包含一个完全组装好的 Authentication 对象,从而使后续鉴权能正常执行 |
SecurityContextHolderAwareRequestFilter | 此过滤器对 ServletRequest 进行了一次包装,使得 request 具有更加丰富的 API |
JaasApiIntegrationFilter | (JAAS) 认证方式 filter |
RememberMeAuthenticationFilter | 记忆认证处理过滤器,即是如果前面认证过滤器没有对当前的请求进行处理,启用了 RememberMe 功能,会从 cookie 中解析出用户,并进行认证处理,之后在 SecurityContextHolder 中存入一个 Authentication 对象。 |
AnonymousAuthenticationFilter | 匿名认证处理过滤器,当 SecurityContextHolder 中认证信息为空, 则会创建一个匿名用户存入到 SecurityContextHolder 中 |
SessionManagementFilter | 会话管理 Filter,持久化用户登录信息,可以保存到 session 中,也可以保存到 cookie 或者 redis 中 |
ExceptionTranslationFilter | 异常处理过滤器,主要拦截后续过滤器(FilterSecurityInterceptor)操作中抛出的异常。 |
FilterSecurityInterceptor | 安全拦截过滤器类,获取当前请求 url 对应的 ConfigAttribute,并调用 accessDecisionManager 进行访问授权决策。 |
spring security 的默认 filter 链:
SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor
在上节我们已分析了核心的 filter 源码以及功能。可回看上节源码分析更加深入的了解各个 filter 工作原理。
总结:
在认证和访问授权过程前,首先必须进行 WebSecurityConfigurer 符合自身应用的 security Configurer,也要清楚 filter 链的先后顺序,才能更好理解 spring security 的工作原理以及在项目中出现的问题定位。了解完准备工作,接下来将展开对认证和访问授权模块的工作流程研究以及项目示例分析。最后如有错误可评论告知。
最后可关注公众号:【Ccww 笔记】一起学习, 每天会分享干货,还有学习视频领取!