在前一篇文章 《Spring Security使用基础》 中讲到了使用Spring Security的基础知识,下面就对其的使用进行拆解说明
1、基本原理
Spring Security中有几个关键的对象需要我们深入理解,下图是他们间的关系
首先,其核心是HttpSecurity
,该对象负责对安全控制的所有方面进行配置,例如配置对哪些路径的访问需要具备哪些角色等。其本身是一个SecurityBuilder类型对象,最终会构建出一个用于拦截所有请求的过滤器链(FilterChian实例对象)。
HttpSecurity的职责是为了构建出一条过滤器链,而这个链中具备哪些Filter则需要对其进行配置。由于其可配置的过滤器有很多,如果都放在HttpSecurity中对过滤器进行创建和设置,那么HttpSecurity将会变得十分笨重且难以维护,对后续的扩展也十分不利。
为了应对上述的问题,随后便出现了许多针对HttpSecurity的配置类
,每个配置类负责对HttpSecurity的某一面进行配置,HttpSecurity则负责统筹和协调。所以你就看到了诸如:HeadersConfigurer、SessionManagementConfigurer、RememberMeConfigurer、AuthorizeHttpRequestsConfigurer等配置类,这些配置类都会负责给HttpSecurity创建Filter和相关类的实例对象。
其次就是WebSecurity
,一个HttpSecurity可以生成一个SecurityFilterChain,但WebSecurity中可将多个SecurityFilterChian包装成一个FilterChainProxy来对外提供服务。
2、SecurityBuilder
在Spring Security中,对象是“构建”出来的,其定义了一个用于构建任意类型实例的接口
public interface SecurityBuilder<O> {
O build() throws Exception;
}
通过调用以上的build()方法来构建指定泛型类型的实例对象。基于该接口,还定义了一个抽象类
public abstract class AbstractSecurityBuilder<O>
implements SecurityBuilder<O> {
@Override
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has "+
"already been built");
}
protected abstract O doBuild() throws Exception;
}
考虑到对象的创建过程中可能还需要经过一些类的配置(例如上面讲到的HttpSecurity在构建FilterChian实例对象的过程中需要进行配置),所以又衍生出了以下的抽象类,该抽象类将对象的构建分成了多个阶段,可以在各阶段执行对应的动作
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
//SecurityConfigurer本身也是SecurityBuilder类型
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit();
init();
this.buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
this.buildState = BuildState.BUILDING;
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
protected void beforeInit() throws Exception {
}
protected void beforeConfigure() throws Exception {
}
protected abstract O performBuild() throws Exception;
private void init() throws Exception {
//以自身为参数调用每个配置类
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
//以自身为参数调用每个配置类
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
List<SecurityConfigurer<O, B>> result = new ArrayList<>();
for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
result.addAll(configs);
}
return result;
}
}
这里可以看到有一个SecurityConfigurer
类型,从名字可见,这应该是一个用于起到配置作用的类型,定义如下
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
该配置类型,用于对构建O类型实例对象的SecurityBuilder类型(例如构建FilterChian的HttpSecurity)进行配置。这里需要注意的是,在这接口中定义的方法的参数为“要对其进行配置的SecurityBuilder”,这样在SecurityConfigurer中就可以通过方法的参数来回调被配置类的方法了。
对于初次接触的人来讲,这几个类有点不好里理解,我用叙事的方式进行说明下,这种方式就等同于:我是一个SecurityBuilder构造器[HttpSecurity],用于构造A类型[FilterChian],我会创建A类型实例,并对其属性进行设置,然后返回给使用方。
但在给A实例设置属性前,我需要先询问下和我有关系的各个SecurityConfigurer实例希望对其属性怎么设置,当然,我开放了一些窗口来接收大家的意见,同时我也会依次拜访各位SecurityConfigurer,大伙有啥建议可以通过我提供的接口反馈给我,届时我会基于大家的返回来构建A实例对象。
3、HttpSecurity
Spring Security中内置了许多的过滤器,这些过滤器起着不同的作用,我们可以根据需要来构建对应的Filter。其内置的HttpSecurity对象为我们装配Filter提供了入口。
HttpSecurity的作用是为了构建DefaultSecurityFilterChain类型实例对象,一个DefaultSecurityFilterChain实例中包含了多个Filter,每个内置的Filter都可以通过HttpSecurity的各个配置类(SecurityConfigurer)来进行提供。
下面列出部分HttpSecurity的代码,可以看到其内置许多Configurer给我们创建需要的Filter。我们只需要调用HttpSecurity的方法就可以获取到配置类的实例,并进一步根据配置类提供的接口来对Filter进行配置。
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private List<OrderedFilter> filters = new ArrayList<>();
//定义了内置Filter的排序
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
private AuthenticationManager authenticationManager;
public CorsConfigurer<HttpSecurity> cors() throws Exception {
return getOrApply(new CorsConfigurer<>());
}
public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
return getOrApply(new SessionManagementConfigurer<>());
}
public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
return getOrApply(new RememberMeConfigurer<>());
}
public RequestCacheConfigurer<HttpSecurity> requestCache() throws Exception {
return getOrApply(new RequestCacheConfigurer<>());
}
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
return getOrApply(new ExceptionHandlingConfigurer<>());
}
public SecurityContextConfigurer<HttpSecurity> securityContext() throws Exception {
return getOrApply(new SecurityContextConfigurer<>());
}
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
public LogoutConfigurer<HttpSecurity> logout() throws Exception {
return getOrApply(new LogoutConfigurer<>());
}
public AnonymousConfigurer<HttpSecurity> anonymous() throws Exception {
return getOrApply(new AnonymousConfigurer<>());
}
public HttpSecurity authenticationManager(AuthenticationManager authenticationManager) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
return HttpSecurity.this;
}
public <C> void setSharedObject(Class<C> sharedType, C object) {
super.setSharedObject(sharedType, object);
}
public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
getAuthenticationRegistry().authenticationProvider(authenticationProvider);
return this;
}
public HttpSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception {
getAuthenticationRegistry().userDetailsService(userDetailsService);
return this;
}
//添加配置类并返回
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
throws Exception {
//父类AbstractConfiguredSecurityBuilder方法获取
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
//父类AbstractConfiguredSecurityBuilder方法放入集合
return apply(configurer);
}
@Override
protected void beforeConfigure() throws Exception {
if (this.authenticationManager != null) {
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
else {
ObservationRegistry registry = getObservationRegistry();
AuthenticationManager manager = getAuthenticationRegistry().build();
if (!registry.isNoop() && manager != null) {
setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager));
}
else {
setSharedObject(AuthenticationManager.class, manager);
}
}
}
@SuppressWarnings("unchecked")
@Override
protected DefaultSecurityFilterChain performBuild() {
ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
ExpressionUrlAuthorizationConfigurer.class);
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
//最终构建出了FilterChain实例对象
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
}
附:Spring Security中的内置过滤器(部分)
4、WebSecurity
理清了上面的关系后再看WebSecurity就变得简单了。该类的目的就是调用所持有的SecurityBuilder<? extends SecurityFilterChain>类型(例如HttpSecurity)的build()方法来获取SecurityFilterChian,再将所有的SecurityFilterChian包装成FilterChainProxy。
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements SecurityBuilder<Filter>, ApplicationContextAware,
ServletContextAware {
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
@Override
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
//构建出FilterChainProxy
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
else if (!this.observationRegistry.isNoop()) {
CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
new HttpStatusRequestRejectedHandler());
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
}
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (this.debugEnabled) {
this.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);
}
this.postBuildAction.run();
return result;
}
}
5、@EnableWebSecurity
通过HttpSecurity我们完成了安全所需的配置,通过WebSecurity完成了FilterChainProxy的创建,那么他们被执行的入口在哪呢?接下来就看看可以让所有安全配置生效的EnableWebSecurity注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
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;
}
首先看下HttpSecurityConfiguration
配置类,之所以先看这个是因为这里面完成了对HttpSecurity的实例创建,并对其进行了默认的配置
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
//1、重点:HttpSecurity实例被创建了
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
// @formatter:off
http
.csrf(withDefaults())
.addFilter(webAsyncManagerIntegrationFilter)
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyDefaultConfigurers(http);
return http;
}
private AuthenticationManager authenticationManager() throws Exception {
return this.authenticationConfiguration.getAuthenticationManager();
}
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
return this.context.getBean(AuthenticationEventPublisher.class);
}
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}
private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy);
return sharedObjects;
}
}
接着看看WebSecurityConfiguration
配置类
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity;
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
private ClassLoader beanClassLoader;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
//1、重点:HttpSecurityConfiguration创建的HttpSecurity被注入
@Autowired(required = false)
private HttpSecurity httpSecurity;
@Bean
public static DelegatingApplicationListener delegatingApplicationListener() {
return new DelegatingApplicationListener();
}
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return this.webSecurity.getExpressionHandler();
}
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
//2、重点:开始创建WebSecurity实例
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
ConfigurableListableBeanFactory beanFactory) throws Exception {
//创建WebSecurity实例
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
beanFactory).getWebSecurityConfigurers();
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;
}
//扩展点:我们可以自己创建SecurityConfigurer对WebSecurity进行配置
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
this.webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
//3、重点:创建名为springSecurityFilterChain的FilterChainProxy
//并暴露到IOC中【通过暴露到IOC后,便可以被加入到Servlet容器里面】
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
//通过该判断可知,如果IOC中已经有了FilterChian实例,
//则不会再执行WebSecurity的方法来驱动FilterChian的创建
if (!hasFilterChain) {
//HttpSecurity实例被添加到WebSecurity中
//并添加默认配置
this.webSecurity.addSecurityFilterChainBuilder(() -> {
this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
this.httpSecurity.formLogin(Customizer.withDefaults());
this.httpSecurity.httpBasic(Customizer.withDefaults());
return this.httpSecurity.build();
});
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
}
说明:以上只是摘录了部分代码,可自行深入查阅研究
6、简单使用示例
前面对Spring Security的允许原理做了简要的全貌说明,下面我们看看在SpringBoot中如何使用
@Configuration
public class CustomSecurityConfiguration {
//配置全局的忽略需要认证的资源路径,这些资源将绕过安全控制
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/webjars/**", "/icon/**","/js/**","/css/**");
}
//注入HttpSecurity,并对齐进行配置
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
return http.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
//假设你自定义了访问被拒绝时候的处理器
.accessDeniedHandler(new CustomAccessDeniedHandler())
//假设你自定义了认证的入口实现(例如调整到某个登录页面)
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
//授权请求配置(以前版本是authorizeRequests())
.authorizeHttpRequests()
//匹配的路径都可以访问
.requestMatchers("/auth/code","/error","/logout","/forbidden").permitAll()
//具备某个角色的用户才可以访问的路径
.requestMatchers("/admin/**").hasAnyRole("admin")
//其他的资源访问都需要登录
.anyRequest().authenticated()
.and()
//假设你自定义了一个Filter,并添加到UsernamePasswordAuthenticationFilter前面
.addFilterBefore(customAuthenticationFilter,UsernamePasswordAuthenticationFilter.class)
//假设你自定义了认证方式(authenticationManager)
.authenticationManager(authenticationManager)
.build() ;
}
}
本文对Spring Security的主要原理进行了拆解说明,虽然未讲解很多细节的内容,但按照上诉的思路去看源码也就容易了很多。
在后续的文章中还会对“认证”、“鉴权”等方面做详细的分析。Spring Security对认证和鉴权过程进行了诸多的抽象,这些抽象的内容可以很好的回答在 《Spring Security使用基础》 中提出的问题。