网上已有一些对于SpringSecurity的分析解读,写的很好,看过以后受益良多!java
好比:web
https://www.cnkirito.moe/categories/Spring-Security/spring
http://www.spring4all.com/article/428json
有兴趣能够先研读下数组
这里的博客主要是参考他人文章,顺便记录下本身的理解,仅此安全
之前学习过Spring Security的XML配置形式,如今基本都使用Java Config形式了,这点的变化仍是蛮大的session
XML形式:mvc
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这里主要是注册一个过滤器DelegatingFilterProxy类,映射的name为springSecurityFilterChain,直观。app
暂不分析async
Java Config形式:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //... }
能够看到,声明一个配置类,主要特色有:
1:@EnableWebSecurity注解
2:继承WebSecurityConfigurerAdapter类
3:重写config方法
4:HttpSecurity类的使用
因为被@Configuration修饰,不出意外,应该会在Spring启动的时候加载。看下@EnableWebSecurity注解的定义:
@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 { }
是一个多重注解,加载了WebSecurityConfiguration.class,SpringWebMvcImportSelector.class和@EnableGlobalAuthentication注解,继续跟进后者:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import(AuthenticationConfiguration.class) @Configuration public @interface EnableGlobalAuthentication { }
发现其又加载AuthenticationConfiguration.class
综上可知,主要是加载了三个类:
<1>SpringWebMvcImportSelector的做用是判断当前的环境是否包含springmvc,由于spring security能够在非spring环境下使用,为了不DispatcherServlet的重复配置,因此使用了这个注解来区分。
<2> WebSecurityConfiguration顾名思义,是用来配置web安全的,下面的小节会详细介绍。
<3>AuthenticationConfiguration权限配置相关类
而重点就是后二者!
下面启动Spring,跟踪下源码,查看它如何加载的:
上述是启动对应的时序图。
由上所述,咱们主要关注三个起点类:
注解引入:WebSecurityConfiguration,AuthenticationConfiguration
自定义类的父类:WebSecurityConfigurerAdapter
自定义启动类以下:
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.crypto.password.NoOpPasswordEncoder; @Configuration @EnableWebSecurity public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { MyAuthenticationFailureHandler failHander = new MyAuthenticationFailureHandler(); @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // .antMatchers("/") // .permitAll() // 请求路径"/"容许访问 // .anyRequest() // .authenticated() // 其它请求都不须要校验才能访问 .antMatchers("/home") .hasRole("LOGOPR") .and() .formLogin() .loginPage("/login") // 定义登陆的页面"/login",容许访问 .permitAll() .failureUrl("/login?#error=1111") // .failureHandler(failHander) .and() .logout() // 默认的"/logout", 容许访问 .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { System.out.println("--------------AuthenticationManagerBuilder-----------"); // 在内存中注入一个用户名为anyCode密码为password而且身份为USER的对象 auth.inMemoryAuthentication().withUser("username").password("password").roles("USER"); } /* * 问题描述:在写基于Spring cloud微服务的OAuth2认证服务时,由于Spring-Security从4+升级到5+, * 致使There is no PasswordEncoder mapped for the id “null”错误。 * */ @Bean public static NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
debug启动应用,进入AuthenticationConfiguration,生成了一个bean:AuthenticationManagerBuilder,以下:
是一个初始化的状态,里面的Providers为空,parentAuthenticationManager为空,状态为UNBUILT
接着经过setFilterChainProxySecurityConfigurer注入bean
@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; }
其中包括咱们自定义的MyWebSecurityConfig代理类:
能够看到是一个List类型,也说明能够同时自定义多个Config类
接着就new出一个WebSecurity对象,遍历上述List,将自定义MyWebSecurityConfig应用于webSecurity:webSecurity.apply(webSecurityConfigurer)
这里调用的是AbstractConfiguredSecurityBuilder
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers .get(clazz) : null; if (configs == null) { configs = new ArrayList<SecurityConfigurer<O, B>>(1); } configs.add(configurer); this.configurers.put(clazz, configs);
主要是为了初始化:
LinkedHashMap<Class,List<SecurityConfigurer>> configurers,
它存储的是自定义的MyWebSecurityConfig,key是类对应的Class,value是此List<SecurityConfigurer<O, B>>,后续还会用到configurers这个全局变量,这里执行完毕,此初始化方法执行完毕!
继续跟进,直到执行@Bean # springSecurityFilterChain(),产一个过滤器链,这是较为核心的部分!
代码以下:
@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()建立过滤器链,跟进,调用父类AbstractSecurityBuilder#build(),根据时序图,后面会屡次调用此类的build()方法,比较复杂。。。
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"); }
继续调用子类的AbstractConfiguredSecurityBuilder#doBuild()方法:
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); init(); buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); buildState = BuildState.BUILDING; O result = performBuild(); buildState = BuildState.BUILT; return result; } }
看下继承类图:
这四个类穿插不停的执行。。。
继续执行AbstractConfiguredSecurityBuilder#init()方法,这里调用的是它自身的init方法
@SuppressWarnings("unchecked") 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 : configurersAddedInInitializing) { configurer.init((B) this); } }
private Collection<SecurityConfigurer<O, B>> getConfigurers() { List<SecurityConfigurer<O, B>> result = new ArrayList<SecurityConfigurer<O, B>>(); for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) { result.addAll(configs); } return result; }
由前所述及时序图可知,这里的this.configurers已在前面初始化过,value存储的就是自定义的MyWebSecurityConfig,因此这里遍历configurers,实际调用的是MyWebSecurityConfig的init类,因为没有重写init类,因此这里就调用了父类WebSecurityConfigurerAdapter的init()方法:
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); } }); }
接着就调用了getHttp()
@SuppressWarnings({ "rawtypes", "unchecked" }) protected final HttpSecurity getHttp() throws Exception { if (http != null) { return http; } DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); //获取AuthenticationManager对象,这个类在登录的时候会用上,用来产生登录结果:Authentication AuthenticationManager authenticationManager = authenticationManager(); authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); 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); } } //调用子类方法,基本就是自定义的Config类 configure(http); return http; }
这个方法基本就是获取各类配置了:
首先是获取AuthenticationManager对象,这个类能够用来生成Authentication对象,也就是登录最终生成的结果
protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) { configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { authenticationManager = authenticationConfiguration .getAuthenticationManager(); } else { authenticationManager = localConfigureAuthenticationBldr.build(); } authenticationManagerInitialized = true; } return authenticationManager; }
这里是经过authenticationConfiguration.getAuthenticationManager(),这里的authenticationConfiguration是一个全局AuthenticationConfiguration对象,由Spring生成的代理对象:
它的属性AuthenticationManager为null,且未被初始化,调用其getAuthenticationManager()
public AuthenticationManager getAuthenticationManager() throws Exception { if (this.authenticationManagerInitialized) { return this.authenticationManager; } AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder( this.objectPostProcessor, this.applicationContext); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); } for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { authBuilder.apply(config); } authenticationManager = authBuilder.build(); if (authenticationManager == null) { authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return authenticationManager; }
这里调用authBuilder.apply(config),实际是继续初始化AbstractConfiguredSecurityBuilder的全局变量:
LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers
该对象在上面已经初始化过了,后面调用authBuilder.build(),它继续回调AbstractSecurityBuilder,操做的仍然是上述的configurers,比较复杂,略过
其中,在此build()方法中当调用performBuild()时:
@Override protected ProviderManager performBuild() throws Exception { if (!isConfigured()) { logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); return null; } ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager); if (eraseCredentials != null) { providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials); } if (eventPublisher != null) { providerManager.setAuthenticationEventPublisher(eventPublisher); } providerManager = postProcess(providerManager); return providerManager; }
这里就是产生ProviderManager
这里默认的Provider就是DaoAuthenticationProvider,这里未看到如何注入,应该也是以前初始化的吧,建立完毕调用providerManager = postProcess(providerManager),这里调用的是spring的AutowireCapableBeanFactory,目的是将ProviderManager装配成一个bean,最终返回此对象!
一步步返回,回到了authBuilder.build(),获得了AuthenticationManager的实例对象:ProviderManager,这个类在表单登录是是一个很重要的核心类!
在比对下对象authenticationConfiguration
能够看到属性已经不为空,初始化状态变为true了
继续返回到getHttp()方法,进行默认的HttpSecurity配置,略过,直到执行configure(http)方法,这里实际调用的就是自定义的MyWebSecurityConfig的configure方法:
执行前,看下http对象:
属性以下:
buildState = UNBUILT configures = { class org.springframework.security.config.annotation.web.configurers.CsrfConfigurer=[org.springframework.security.config.annotation.web.configurers.CsrfConfigurer@2dd8239], class org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer=[org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer@472698d], class org.springframework.security.config.annotation.web.configurers.HeadersConfigurer=[org.springframework.security.config.annotation.web.configurers.HeadersConfigurer@7b7683d4], class org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer=[org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer@40712ee9], class org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer=[org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer@2e53b094], class org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer=[org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer@39fa8ad2], class org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer=[org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer@76ddd61a], class org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer=[org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer@3f92a84e], class org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer=[org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer@cf67838], class org.springframework.security.config.annotation.web.configurers.LogoutConfigurer=[org.springframework.security.config.annotation.web.configurers.LogoutConfigurer@6137cf6e] } filter = [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56f521c6] RequestMatcher = org.springframework.security.web.util.matcher.AnyRequestMatcher@1
执行完毕以后返回到AbstractSecurityBuilder,init()方法执行完毕,继续执行performBuild(),调用WebSecurity的perfotmBuild():
@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; }
这里首先根据securityFilterChainBuilders的大小建立一个数组,通常都只有一个过滤器链,因此size=1,遍历此对象
就是HttpSecurity对象,上面罗列出的他的configurers属性,包含的是各类Configurer对象,这些对象就是用来生成Filter对象!
遍历调用configurer.build(),仍然是调用AbstractSecurityBuilder的build(),调用太屡次了,不深刻了,最终生成了一个securityFilterChain:
同时生成一个FilterChainProxy,过滤器代理类,后续登录等各类请求的入口就是这个类了,能够参见第二篇登录流程介绍,debug开始的地方就是FilterChainProxy#doFilter,这里真是一个典型的Filter模式实现!
至此,springSecurityFilterChain()执行完毕,返回的就是一个Filter:springSecurityFilterChain,做为一个Bean!
生成完这个对象,初始化流程最重要的事情就完成了,后续主要就是在这个过滤器链中处理各类请求!