概述:
Spring Security就是引入了一系列的SecurityFilter,将其添加到Spring中去了;在有请求时,根据URL是否符合每个Filter的规则来判断是否需要该Filter来进行处理。
我们先来看下Security的加载过程,在下文将详细说明该加载过程是如何分析出来的:
0. 示例:
示例代码地址:(https://github.com/icarusliu/Test)
为使用itellij的工程;
其入口为SecurityConfig类; :
package com.liuqi.tool.todo.common.security; /**
* Created by icaru on 2017/7/2.
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* <p>
* </p>
*
* @Author icaru
* @Date 2017/7/2 23:12
* @Version V1.0
* --------------Modify Logs------------------
* @Version V1.*
* @Comments <p></p>
* @Author icaru
* @Date 2017/7/2 23:12
**/
@EnableWebSecurity(debug = true)
public class SercurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TestAuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll()
.and()
.sessionManagement().invalidSessionUrl("/timeout");
http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Bean
public UserDetailsService getUserDetailservice() {
return new TestUserDetailService();
}
}
要使Spring Security生效,主要是需要配置@EnableWebSecurity注解;
运行工程中的Application的Main函数即可启动Spring Boot;启动完成后通过浏览器打开http://localhost即可看到首页,可以点击用户管理等后台管理模块会要求进行登录; 使用admin/123456登录即可看到效果。
注意在EnableWebSecurity上指定了Debug为True;此时在启动日志里面可以看到以下日志:
2017-11-30 16:08:38.371 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@466f1fc3, org.springframework.security.web.context.SecurityContextPersistenceFilter@75b52145, org.springframework.security.web.header.HeaderWriterFilter@55e7044d, org.springframework.security.web.csrf.CsrfFilter@120c3a8a, org.springframework.security.web.authentication.logout.LogoutFilter@1ad0d698, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@77b3a17c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@58f5459, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6827fdc9, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@386ddda2, org.springframework.security.web.session.SessionManagementFilter@69e9376, org.springframework.security.web.access.ExceptionTranslationFilter@6702f90b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7c7258c6]
2017-11-30 16:08:38.494 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern=’/h2-console/**’], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56985854, org.springframework.security.web.context.SecurityContextPersistenceFilter@11b6e572, org.springframework.security.web.header.HeaderWriterFilter@4df71a65, org.springframework.security.web.authentication.logout.LogoutFilter@7e2ca6e2, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@58174120, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7bad0a32, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@efc0310, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@614ec113, org.springframework.security.web.session.SessionManagementFilter@5ac55cb9, org.springframework.security.web.access.ExceptionTranslationFilter@78924c6a, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5c8ea323]
2017-11-30 16:08:38.494 WARN 4964 — [ restartedMain] o.s.s.c.a.web.builders.WebSecurity :
** Security debugging is enabled. *****
** This may include sensitive information. *****
** Do not use in a production system! *****
可以看到Security启动时加载的各个Filter;
2 引入:
自定义一个类,在其上添加@EnableWebSecurity注解即可;
有两种方式:
一是直接在入口类中加上该注解,入口类不需要继承自WebSecurityConfigurerAdapter
二是添加注解后再继承自WebSecurityConfigurerAdapter类; 继承自该类后,将会自动添加如表单登录、记住用户名密码等十来个个Filter
这些Filter是在HttpSecurity中定义的; 而HttpSecurity又是在WebSecurityConfigurerAdapter中创建的使用的
为什么添加了EnableWebSecurity注解后Spring Security即启动了呢?
我们看下这个注解的定义:
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configuration;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
其重点就是@Import中将WebSecurityConfiguration类引入; 同时自己也被Configuration所注解;
@Import的含义就在于,它相当于以前基于XML配置方式的一个配置文件,配置在WebSecurityConfiguration中的Bean以及WebSecurityConfiguration对象本身都会被Spring容器所托管;
接下来我们研究下WebSecurityConfiguration类; 来分析下SpringSecurity启动后做了什么。
3 WebSecurityConfiguration
WebSecurityConfiguration对象做了以下事情:一是创建了一个WebSecurity对象;二是通过WebSecurity对象创建了名称为springSecurityFilterChain 的Filter并托管到Spring容器中,在springSecurityFilterChain 这个Filter中,添加到一些默认的Security的Filter;
3.1 创建WebSecurity对象
首先来看WebSecurity对象的创建,在方法setFilterChainProxySecurityConfigurer 中; 其实现如下:
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
主要做了两个事情,一是通过New的方式创建了个WebSecurity对象;二是将一些默认的SecurityConfigurer对象从Spring容器中获取到并添加到WebSecurity对象中;
注意其中webSecurityConfigurers 的值,是通过@Value注入的方法获取的值,其方法如下所示:
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
该方法在Spring容器中查找所有的类型为WebSecurityConfigurer的Bean,将其添加到List中并返回;
我们再回过头来看WebSecurityConfigurerAdapter类,可以看到其实现WebSecurityConfigurer类;因此,adapter就在这个地方被加载到webSecurityConfigurers中去了; 最终被添加到WebSecurity对象中去了。
3.2 创建名称为:springSecurityFilterChain 的Filter并托管到Spring容器;
该步骤通过WebSecurityConfiguration类的 setFilterChainProxySecurityConfigurer 方法执行:
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
这个方法就是通过WebSecurity的Build方法,生成了一个Filter并注入到Spring容器中;
完成这个步骤后,当SpringMVC获取到URL请求时,就会调用该Filter来看是否需要交由该Filter处理该请求; 这部分内容已经不属于Security内容,因此在此暂时不进行研究。
这个里面还有个疑问,如果要保证在执行springSecurityFilterChain 方法时,WebSecurity已经初始化,那setFilterChainProxySecurityConfigurer 方法就必须要在其前面执行。分析这两个方法,第一个为Bean注解,第二个为Autowired注解,两者唯一的区别在于这两个注解不一样,那是否可以理解为Autowired注解会在Bean注解前执行?这个留待验证!
实际上经过以上分析,SpringSecurity的启动过程基本已经分析完成;
接下来我们可以深入分析下WebSecurity对象所做的一些事情,以及Security加载的默认的Filter在哪控制以及如何生效的;
4 WebSecurity分析
WebSecurity对象在WebSecurityConfiguration中初始化后,在生成名称为springSecurityFilterChain 的Filter时,会调用其Build方法;
Build方法实际上为WebSecurity的父类提供的方法,最终调用的为其本身的performBuild 方法:
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
该方法实际返回的对象为FilterChainProxy对象; 添加的为securityFilterChainBuilders中的对象Build所产生的Filters;
在以下代码:
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
将securityFilterChainBuilders属性中的对象构建产生的Filter添加到结果中;
其中该属性的初始化是在addSecurityFilterChainBuilder方法中进行的:通过该方法的调用逻辑查找,可以看到它是在WebSecurityConfigurerAdapter的Init方法中调用的:
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
其具体实现如下:
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的securityFilterChainBuilders属性中去了;
init方法在WebSecurityConfigurerAdapter对象初始化的时候即会执行,也即在WebSecurityConfigurerAdapter对象初始化的时候就会将HttSecurity对象添加到WebSecurity的Builders属性中去;而WebSecurityConfigurerAdapter对象的引入过程在3.1步骤中已详细说明;
最终在WebSecurity对象的performBuild 方法中,将httpSecurity对象Build产生的Filter添加到SecurityFilterChain中去了。
那么HttpSecurity对象Build产生的Filter是什么内容? 下一步将了解这部分内容;
5 HttpSecurity功能
HttpSecurity用于提供一系列的Security默认的Filter,最终在WebSecurity对象中,组装到最终产生的springSecurityFilterChain 对象中去;
其主要包含两个过程:
一是生成默认的FilterConfigurer对象并添加到其filters属性中存储 ;
二是调用其performBuild方法生成DefaultSecurityFilterChain对象;
以LoginForm为列,其添加FilterConfigurer的方法如下:
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<HttpSecurity>());
}
这个方法的调用入口在WebSecurityConfigurerAdapter的Configure方法中 ; 如果工程提供的继承该类的类中覆盖了该方法,则会在视情况在这个方法中进行调用。
如示例工程:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll()
.and()
.sessionManagement().invalidSessionUrl("/timeout");
http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
}
其performBuild方法如下:
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
实际就组装了一个DefaultSecurityFilterChain对象并返回 ;
下一步探讨在HttpSecurity中添加进去的Filter怎么样生效。
6 WebSecurity对象的performBuild 方法中,返回的是FilterChainProxy对象,其实际包含了一个SecurityFilterChain 的列表对象 ;
先来看FilterChainProxy的关键方法doFilter:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
其实际调用的为doFilterInternal :
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
该方法中,第一步为根据请求查找满足条件的Filters,第二步为根据找到的Filters来创建一个VirtualFilterChain对象,最终调用其doFilter方法;
其doFilter方法如下:
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
第一步判断是否additionalFilters中的Filter全部执行完毕,如果没有执行完毕则继续执行其中的Filter;否则执行OriginalChain中的Filters;
这里通过游标的方式判断当前执行的是第几个Filter; 注意最后一步nextFilter.doFilter的时候,将VirtualFilterChain对象传递到了要执行的Filter中; 然后在执行的Filter中决定是否要继续调用FilterChain中的下一个Filter;以LogoutFilter为例,其doFilter方法如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
可以看到,先判断是否是退出登录请求,如果是的话则进行登出处理;此时不再执行Chain中的其它Filter; 如果不是才执行Chain中的其它Filter;
由此可见,为什么要使用FilterChain而不是For循环的方式来执行所有Filter,也是为了在Filter中方便的控制下面的Filter是否要继续执行。