Spring Security原理篇(三) HttpSecuri
1.初始化HttpSecurity对象
从前面的文章中,我们已经提到在WebSecurityConfigurerAdapter
的初始化方法init()
中,通过getHttp()
方法获取到了HttpSecurity
的对象,我们再来看一下init()
这个方法的源代码
/**
* @param web
* @throws Exception
*/
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
这个方法的具体作用已经在上一篇文章中说过,先构建HttpSecurity
对象,然后通过WebSecurity
对象的addSecurityFilterChainBuilder()
方法添加到securityFilterChainBuilders的List中,最后用来组件过滤器链。
1.1 WebSecurityConfigurerAdapter
的getHttp()
方法
- 首先我们还是引入一下这个方法的源代码
protected final HttpSecurity getHttp() throws Exception {
//如果已经存在HttpSecurity 对象,则返回
if (http != null) {
return http;
}
//这里主要还是关于异常的一些处理,这里我们最后的文章统一再说,先给自己留个坑,先猜测一下吧
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
//构建AuthenticationManager对象,这个对象管理认证,后面我们再说
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
//创建共享对象
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
//构建HttpSecurity 需要用到authenticationBuilder,sharedObjects
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
//允许默认配置的时候
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.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??
configure(http);
return http;
}
- 我们可以从上面的代码中可以知道,因为
HttpSecurity
构造函数需要AuthenticationManagerBuilder
和sharedObjects
对象,上面的代码先创建AuthenticationManagerBuilder
的对象,然后填充了共享对象的map,然后调用HttpSecueity
的构造函数构造出来一个HttpSecurity的对象,然后configure(http),这个方法最后再讲,关于AuthenticationManagerBuilder
我们到用户认证的时候再去专门讲,而sharedObjects
我们也需要专门篇幅来将讲解,这些看似细节又复杂的东西,怕混乱到影响这篇文章重点需要讲解的HttpSecurity
1.2 HttpSecurity
类的大体理解
1.2.1 HttpSecurity
的类图
HttpSecurity类图
- 在说Filter的时候我们说到过
WebSecurity
的build()
方法的时候有很具体的说到AbastractSecurityBuilder
和AbstractConfiguredSecurityBuilder
这两个类中build()
方法执行的过程我们看一下HttpSecurity
类的定义
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity>
我们可以知道调用HttpSecurity
的build()
方法的时候返回的就是DefaultSecurityFilterChain
的对象,当然具体的还要看HttpSecurity
的performBuild()
方法
-
HttpSecurityBuilder
接口
1.2.2HttpSecurity
的属性
//从变量名就可以看到是请求匹配过滤的配置信息
private final RequestMatcherConfigurer requestMatcherConfigurer;
//过滤器列表?
private List<Filter> filters = new ArrayList<>();
//匹配任何请求的匹配器
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
//过滤器比较.....啥啥啥不知道
private FilterComparator comparator = new FilterComparator();
- 带着疑问,我们还是要看先了解一下这些属性的值是怎么来的,先挑软柿子捏吧
- 1.
requestMatcher
可以完整的看一下AnyRequestMatcher
的定义
public final class AnyRequestMatcher implements RequestMatcher {
public static final RequestMatcher INSTANCE = new AnyRequestMatcher();
public boolean matches(HttpServletRequest request) {
//直接return true?那就是说有请求都匹配
return true;
}
@Override
@SuppressWarnings("deprecation")
public boolean equals(Object obj) {
return obj instanceof AnyRequestMatcher
|| obj instanceof org.springframework.security.web.util.matcher.AnyRequestMatcher;
}
@Override
public int hashCode() {
return 1;
}
@Override
public String toString() {
return "any request";
}
private AnyRequestMatcher() {
}
}
这个类继承的接口只有matches方法,方法的注释上写的很明白,如果匹配规则就返回true,否则返回false,AnyRequestMatcher
永远返回true,说明匹配任何请求。如果需要查看接口定义,可以自行查看类RequestMatcher
的源代码,因为简单,节省空间。
*2.comparator
是FilterComparator
的对象。然而Filter比较器类的定义也是比较简单的,此处还是不引入源代码了,因为后面我们还要讲解,里面很重要的定义了一些我们不知不觉用着的东西。FilterComparator
实现Comparator
接口,作为比较器,我们只看一下他的一个方法就可以了
public int compare(Filter lhs, Filter rhs) {
Integer left = getOrder(lhs.getClass());
Integer right = getOrder(rhs.getClass());
return left - right;
}
这个getOrder就是Filter上定义Order的数字,还有一种形式就是addFilter这个的。反正就是获取到在过滤器列表中的顺序,不过当然不是连续的顺序
- 3.
filters
的list 只是通过addFilter()方法添加进来,放一下源代码,不解释了
public HttpSecurity addFilter(Filter filter) {
Class<? extends Filter> filterClass = filter.getClass();
if (!comparator.isRegistered(filterClass)) {
throw new IllegalArgumentException(
"The Filter class "
+ filterClass.getName()
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
}
this.filters.add(filter);
return this;
}
1.2.3 HttpSecurity的部分方法
- 1.构造方法
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
AuthenticationManagerBuilder authenticationBuilder,
Map<Class<? extends Object>, Object> sharedObjects) {
super(objectPostProcessor);
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
.entrySet()) {
setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
}
ApplicationContext context = (ApplicationContext) sharedObjects
.get(ApplicationContext.class);
this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
}
除了设置了共享对象之外,唯一值得一提的就是ApplicationContext 作为共享对象传递进来了,哈哈,毕竟spring security再牛逼,再spring面前还是得装装小媳妇的。至于requestMatcherConfigurer还是要在稍后的篇幅重点讲一下的,毕竟太重要了,不论对于我们使用还是要理解这个过程,都不可获取。
-
performBuild()
方法,上面已经提到了,主要是为了构建过滤器链,还是看一下源代码吧
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
- 对过滤器进行排序,然后返回了创建的DefaultSecurityFilterChain对象
1.2.4 HttpSecurity
实现HttpSecurityBuilder
的方法
-
authenticationProvider()
方法
这个方法不准备列举源代码了,就是提供一个设置AuthenticationProvider
的方法,至于为什么要说一下,就是为了支出HttpSecurity
可以设置AuthenticationProvider
至于说AuthenticationProvider
有什么用,以后具体说,但是这里还是想先大概说一下,他是具体的用户验证的方式,比如用户名密码形式,邮箱密码形式,短信验证码形式的登录等等吧。 -
userDetailsService()
方法
设置userDetailsService()后面也会说到,主要有一个方法根据username去获取用户信息。然后根据获取到的用户比对密码是否正确的。后面再说吧,先简单提一下 -
addFilterAfter()
方法
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
comparator.registerAfter(filter.getClass(), afterFilter);
return addFilter(filter);
}
-
先注册到过滤器比较器里面,因为要排序
-
然以后添加到过滤器列表中
-
addFilterBefore
方法,参考addFilterAfter()
方法吧,差不多 -
addFilter
方法 自动排序的过滤器,但是从注释上我们必须认识到,添加的过滤器必须继承自一下的某一个过滤器
* <ul>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link LogoutFilter}</li>
* <li>{@link X509AuthenticationFilter}</li>
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
* <li><a href="{@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link OpenIDAuthenticationFilter}</li>
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link DigestAuthenticationFilter}</li>
* <li>{@link org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter}</li>
* <li>{@link BasicAuthenticationFilter}</li>
* <li>{@link RequestCacheAwareFilter}</li>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* <li>{@link JaasApiIntegrationFilter}</li>
* <li>{@link RememberMeAuthenticationFilter}</li>
* <li>{@link AnonymousAuthenticationFilter}</li>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ExceptionTranslationFilter}</li>
* <li>{@link FilterSecurityInterceptor}</li>
* <li>{@link SwitchUserFilter}</li>
* </ul>
1.2.4 HttpSecurity
配置的部分方法
这部分还是参考一下源代码吧,太多太多了,其实在我们使用的时候也可以参考,因为这些方法的注释上都给出了具体的例子,下面简单的看一下吧,就比如formLogin()
这个方法
/**
*
* 指定支持基于表单的身份验证. If
* 若果没有指定loginPage()这个配置,那么将使用默认的登录页面
*
* <h2>Example Configurations</h2>
* 默认的登录的URL为 /login
* <pre>
* @Configuration
* @EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
* }
* }
* </pre>
*
* The configuration below demonstrates customizing the defaults.
*
* <pre>
* @Configuration
* @EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
* }
* }
* </pre>
*
* @see FormLoginConfigurer#loginPage(String)
*
* @return the {@link FormLoginConfigurer} for further customizations
* @throws Exception
*/
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
2 从配置到Filter
我们还是需要看一下HttpSecurity
的配置最后是怎样影响到我们的过滤器执行的
我们还是用两个简单的例子来看一下工作原理
2.1 formLogin()的原理
2.1.1 formLogin配置的例子
我们也不需要自己手写一个例子出来,然后说一大堆,我们直接可以拿到方法上面注释的例子来看一下就可以了,只是简单的替换掉了转义的字符和一点点英文的注释
下面的配置演示了自定义默认值。
@Configuration
@EnableWebSecurity
public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
.usernameParameter("username") //默认的用户名的参数 username
.passwordParameter("password") // 默认的密码的参数 password
.loginPage("/authentication/login") // 默认请求地址 /login with an HTTP get
.failureUrl("/authentication/login?failed") //默认失败地址 /login?error
.loginProcessingUrl("/authentication/login/process"); // default is /login // with an HTTP
// post
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
上面的例子做了一下几件事情
- 在内存中创建了一个用户,用户名user,密码password,拥有角色USER
- 配置了任何请求的用户都必须拥有USER角色
- 配置了登录请求的地址为
/authentication/login
,失败的地址为/authentication/login?failed
,用户名提交的参数为username
,密码提交的参数为password
,虽然用户名和密码的参数默认也是这样,但是我们也知道这里可以自定义
2.1.2 formLogin背后的实现原理
- 我们看一下在
HttpSecurity
中formLogin()的方法
//formlogin()方法
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
//T1这里的FormLoginConfigurer和getOrApply方法我们都不知道是啥玩意儿
return getOrApply(new FormLoginConfigurer<>());
}
//getOrApply()方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
C configurer) throws Exception {
//T2 这里的意思是去查看是否有存在了
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
//T3 不存在的情况下就去调用apply()这个方法
return apply(configurer);
}
//apply()方法
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
//T4
configurer.addObjectPostProcessor(objectPostProcessor);
//T5
configurer.setBuilder((B) this);
//T6
add(configurer);
return configurer;
}
- T1 这里的
FormLoginConfigurer
后面下面说 - T2 这里的C泛型指的就是
FormLoginConfigurer
,getConfigurer()
方法的源代码自行查看,这个方法大概的意图就是LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers
在这个map中如果存在这个FormLoginConfigurer的配置就返回,否则返回null - T4 这里添加一个后置处理器,先不说
- T5 设置this到builder里面,后面会有用
- T6 将FormLoginConfigurer添加到
LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers
的map中
2.1.3 FormLoginConfigurer
类
-
类图
FormLoginConfigurer类图 -
我们看一下构造方法
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
调用父类构造方法的时候创建了UsernamePasswordAuthenticationFilter
,然后复制给了一个叫做authFilter
的变量,所以我们知道,其实在创建FormLoginConfigurer的时候,他自己就已经有了一个叫做UsernamePasswordAuthenticationFilter
-
FormLoginConfigurer
向Filter
转换的configure()
方法
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
authenticationEntryPoint.setPortMapper(portMapper);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
this.defaultSuccessHandler.setRequestCache(requestCache);
}
authFilter.setAuthenticationManager(http
.getSharedObject(AuthenticationManager.class));
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if (authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http
.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(authFilter);
http.addFilter(filter);
}
这段代码大部分的都是在设置一些Filter执行需要的属性。这个和具体的这个配置到底是完成什么样的功能有关系,然后我们最需要关心的只有下面这行代码
http.addFilter(filter);
也就是说最后往HttpSecurity的List<Filter> filters
列表中添加了一个Filter对象
-
触发FormLoginConfigurer的configure()方法调用
HttpSecurity调用build()方法的时候回调用dobuild()方法,然后configure()方法
这个configure方法回调用HttpSecurity的所有的configure方法,然后转换成过滤器添加到filters方法中
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
至于说这个configurers属性我们前面说过每一个配置后面调用apply()方法都会添加到这个列表中,这里不再赘述
2.2 直接添加过滤器
上面添加过滤器是通过对HttpSecurity方法的调用实现配置,最后添加过滤器,然而直接添加过滤器就更加简单,下面只通过一个简单的例子来说一下
2.2.1 addFilterAfter方法
直接看一下源代码就行,因为实在太简单了
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
comparator.registerAfter(filter.getClass(), afterFilter);
return addFilter(filter);
}
public HttpSecurity addFilter(Filter filter) {
Class<? extends Filter> filterClass = filter.getClass();
if (!comparator.isRegistered(filterClass)) {
throw new IllegalArgumentException(
"The Filter class "
+ filterClass.getName()
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
}
this.filters.add(filter);
return this;
}
我们可以看到只是先确定过滤器的顺序,然后看一下过滤器是否注册了,然后就会添加到我们的filters这个变量。
3 总结
杂乱无章的几乎介绍了这个类的所有的代码,我们总结一下这个类吧
*HttpSecurity
最终可以得到一个DefaultSecurityFilterChain
通过的是build()
方法
-
HttpSecurity
维护了一个过滤器的列表,这个过滤器的列表最终放入了DefaultSecurityFilterChain
这个过滤器链中 -
HttpSecurity
最终提供了很多的配置,然而所有的配置也都是为了处理维护我们的过滤器列表