Spring Security解析一:安全配置过程概览
在spring-security-config包中有个用于使用JavaConfig的方式启动框架的注解
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
/**
* Add this annotation to an {@code @Configuration} class to have the Spring Security
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending
* the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
*
* @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class,
HttpSecurityConfiguration.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;
}
添加该注解到@Configuration的类上,应用程序便可以使用自定义的WebSecurityConfigurer或拓展自WebSecurityConfigurerAdapter的配置类来装配Spring Security框架。
1,WebSecurityConfiguration配置类
该配置类的目的是在启动时收集BeanFactory中所有的SecurityConfigurer和SecurityFilterChain,然后将收集的SecurityConfigurer和SecurityFilterChain交给WebSecurity来构造出名为springSecurityFilterChain的Filter(FilterChainProxy)并加入IOC容器,从而将Spring Security安全配置融入到应用中。
/**
* Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
* based security for Spring Security. It then exports the necessary beans. Customizations
* can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
* and exposing it as a {@link Configuration} or implementing
* {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
* configuration is imported when using {@link EnableWebSecurity}.
*
* @see EnableWebSecurity
* @see WebSecurity
*
* @author Rob Winch
* @author Keesun Baik
* @since 3.2
*/
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity;
private Boolean debugEnabled;
//收集保存所有的SecurityConfigurer配置
//SecurityConfigurer:WebSecurityConfigurer、WebSecurityConfigurerAdapter
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
private ClassLoader beanClassLoader;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
//在后续的代码中会使用到,例如会话管理部分
//其本身是个ApplicationListener,监听ApplicationEvent事件
//然后将事件转发给内部注册的合适的SmartApplicationListener
//注意:ApplicationEvent是一个比较顶级的事件类型,其下有各种具体类型的事件对象
@Bean
public static DelegatingApplicationListener delegatingApplicationListener() {
return new DelegatingApplicationListener();
}
//当在jsp中使用security相关的标签时有用(需要引入spring-security-taglibs包)
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return webSecurity.getExpressionHandler();
}
/**
* 注意:这是一个Setter方法并且添加了@Autowired注释,所以会最先执行;
* SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
//实例化WebSecurity对象
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
//进行排序【可以有多个SecurityConfigurer,但是order不能有相同的】
webSecurityConfigurers.sort(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;
}
// 配置WebSecurity途径之一(可致使不会使用默认的WebSecurityConfigurerAdapter进行装配)
//注意:我们时常继承WebSecurityConfigurerAdapter来进行配置,而WebSecurityConfigurerAdapter
//也是实现了SecurityConfigurerFilter, WebSecurity>接口。
// 所以我们是可以有多个继承了WebSecurityConfigurerAdapter的配置类的哦~~
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
//保存到webSecurity的configurers变量中
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
securityFilterChains.sort(AnnotationAwareOrderComparator.INSTANCE);
this.securityFilterChains = securityFilterChains;
}
@Autowired(required = false)
void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
webSecurityCustomizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.webSecurityCustomizers = webSecurityCustomizers;
}
/**
* Creates the Spring Security Filter Chain
*【默认的Filter的名称为springSecurityFilterChain】
* 注意:在SpringBoot中通过自动化配置会通过该名称来创建DelegatingFilterProxyRegistrationBean,
* 从而在初始化阶段被注册到Servlet容器中。
*/
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
//判断当前IOC中是否有SecurityConfigurer,如果没有则使用WebSecurityConfigurerAdapter作为默认配置
//webSecurityConfigurers由上面的setFilterChainProxySecurityConfigurer方法执行后得出
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
boolean hasFilterChain = !securityFilterChains.isEmpty();
if (!hasConfigurers && !hasFilterChain) { //如果没有找到SecurityConfigurer类型的Bean则执行默认配置
//使用WebSecurityConfigurerAdapter作为配置
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
//添加到WebSecurity中
webSecurity.apply(adapter);
}
//--------------------------------------------------------------------------------//
// 构建前调整WebSecurity途径之一:将IOC容器中得到的所有SecurityFilterChain类型的Bean进行逐个处理
for (SecurityFilterChain securityFilterChain : securityFilterChains) {
//添加返回SecurityFilterChain的SecurityBuilder
webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
break;
}
}
}
//构建前调整WebSecurity途径之一:对WebSecurity进行自定义
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
//--------------------------------------------------------------------------------//
//构建过滤器链
return webSecurity.build();
}
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
}
AutowiredWebSecurityConfigurersIgnoreParents
/**
* A class used to get all the {@link WebSecurityConfigurer} instances from the current
* {@link ApplicationContext} but ignoring the parent.
*
* @author Rob Winch
*
*/
final class AutowiredWebSecurityConfigurersIgnoreParents {
//从BeanFactory中得到所有WebSecurityConfigurer类型的Bean
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
//返回Bean的名称和Bean实例
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
//保存所有WebSecurityConfigurer类型的Bean
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
}
2,WebSecurity
由于继承关系,当调用WebSecurity的build()方法时其实是在执行AbstractConfiguredSecurityBuilder中的doBuild()方法, doBuild()方法是个模板方法,里面依次调用了beforeInit()、init()、beforeConfigure()、configure()和performBuild()方法并返回结果。
- beforeInit()与beforeConfigure()两个方法默认不做任何事情;
- init()与configure()两个方法默认是以WebSecurity实例为参数逐个执行上面过程中收集的SecurityConfigurer类型实例的init(builder)与configure(builder)方法;
- performBuild()方法实际是执行WebSecurity中的performBuild()方法;
其继承关系图如下所示:
WebSecurity继承关系public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
private final List<RequestMatcher> ignoredRequests = new ArrayList<>();
//SecurityConfigurer类型实例执行完configure(builder)方法完成装配后将结果添加到该集合中
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();
private IgnoredRequestConfigurer ignoredRequestRegistry;
private FilterSecurityInterceptor filterSecurityInterceptor;
private HttpFirewall httpFirewall;
private boolean debugEnabled;
//当在jsp中使用security相关的标签时有用(需要引入spring-security-taglibs包)
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler;
private Runnable postBuildAction = () -> {
};
//SecurityConfigurerAdapter中会设置该Runnable,
//并在实现中调用下面的securityInterceptor设置拦截器
public WebSecurity postBuildAction(Runnable postBuildAction) {
this.postBuildAction = postBuildAction;
return this;
}
/**
* Sets the {@link FilterSecurityInterceptor}. This is typically invoked by
* {@link WebSecurityConfigurerAdapter}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
return this;
}
/**
* 该方法被 WebSecurityConfiguration 调用创建bean
*/
public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
if (privilegeEvaluator != null) {
return privilegeEvaluator;
}
return filterSecurityInterceptor == null ? null
: new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
}
/**
* Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is not specified,
* then a {@link DefaultWebInvocationPrivilegeEvaluator} will be created when
* {@link #securityInterceptor(FilterSecurityInterceptor)} is non null.
*
* @param privilegeEvaluator the {@link WebInvocationPrivilegeEvaluator} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity privilegeEvaluator(
WebInvocationPrivilegeEvaluator privilegeEvaluator) {
this.privilegeEvaluator = privilegeEvaluator;
return this;
}
@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<>(
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;
}
}
在这里完成了名为springSecurityFilterChain的FilterChainProxy的创建(本身是个Filter),它是SecurityFilterChain的代理对象,里面包含了多个SecurityFilterChain的集合。
后面通过DelegatingFilterProxyRegistrationBean【ServletContextInitializer类型】实例对象来将这里的FilterChainProxy添加到Servlet容器中,从而实现请求的过滤操作
附:SecurityBuilder与WebSecurityConfigurer接口定义
public interface SecurityBuilder<O> {
/**
* 构建并返回对象【这里其实就是构建Filter】
*/
O build() throws Exception;
}
/*
* WebSecurityConfigurer接口接受泛型,将使用实现了SecurityBuilder接口的 B 来构造出 O 类型的实例。
*/
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
/**
* 用户初始化SecurityBuilder类型对象
*/
void init(B builder) throws Exception;
/**
* 给SecurityBuilder类型对象设置必要的属性
*/
void configure(B builder) throws Exception;
}
小结
SpringSecurity初始化的意图和主题思想还是相对比较容易理解:
核心过程上图中其实主要做了以下几件事:
- WebSecurityConfiguration创建WebSecurity实例
- WebSecurity中持有多个SecurityBuilder<SecurityFilterChain>类型实例
- WebSecurity迭代执行所有的SecurityBuilder实例得到SecurityFilterChain实例集合
- WebSecurityConfiguration调用WebSecurity的build方法将返回的SecurityFilterChain实例集合封装到FilterChainProxy对象中
可见,上面的过程其实就是为了将各个进行安全处理的Filter创建或收集起来封装到SecurityFilterChain中,再构建出FilterChainProy实例的过程。
那么如何配置和创建出SecurityFilterChain就是需要真正关注的问题了。在SpringSecurity中定义了SecurityBuilder<O> 接口来构建各种类型的组件,而构建SecurityFilterChain实例的接口则为 SecurityBuilder<? extends SecurityFilterChain>
SecurityBuilder<SecurityFilterChain> 会被WebSecurity实例调用返回SecurityFilterChain对象,那么WebSecurity又是如何持有SecurityBuilder类型实例对象的引用的呢?其实可以有多种方式来达到这个目的
- 执行WebSecurity的方法,将SecurityBuilder添加进去
- 在WebSecuriy中直接获取IOC中所有SecurityBuilder类型的实例并添加进来
除此以外,我们还可以直接将构造好的SecurityFilterChain加入WebSecurity中
- 执行WebSecurity的方法,直接将构造好的SecurityFilterChain实例加进去
- 在WebSecuriy中直接获取IOC中所有SecurityFilterChain实例添加进来
我们可以将SecurityConfigurer<O, B>类型实例添加到WebSecurity持有的集合中,在执行期间WebSecurity将自己作为参数执行各个SecurityConfigurer<O, B>的init(B)和configure(B)方法。既然WebSecurity将自己作为参数,那么在SecurityConfigurer<O, B>中不就可以创建好SecurityBuilder<? extends SecurityFilterChain>类型实例并添加进WebSecurity中了(后续要解读的HttpSecurity对象就是SecurityBuilder类型实例)。
其实WebSecurityConfiguration在运行时就会从IOC容器中获取SecurityConfigurer类型对象并添加到WebSecurity中,如果IOC容器中没有找到,则会使用默认的WebSecurityConfigurerAdapter。
我们在开发中往往会通过继承WebSecurityConfigurerAdapter来实现自定义的配置,并同时将其加入到IOC容器中,如此一来也就完成了上面的过程。
以上便是Sprng Security简单的安全配置过程,里面涉及的细节将在后面逐一展开讨论。
附图:WebSecurity