在认证过程和访问授权前必须了解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的工作原理以及在项目中出现的问题定位。了解完准备工作,接下来将展开对认证和访问授权模块的工作流程研究以及项目示例分析。最后如有错误可评论告知。