摘要:前言我们知道的核心实现原理都是从开始的,通过构造层层来实现登录跳转权限验证,角色管理等功能。本章通过剖析的核心源码来说明的是如何开始构造并运行的。
前言
我们知道Spring Security的核心实现原理都是从filter开始的,Spring Security通过构造层层filter来实现登录跳转、权限验证,角色管理等功能。本章通过剖析Spring Security的核心源码来说明Spring Security的filter是如何开始构造并运行的。
从最初开始往往我们定义一个Spring Security程序都是通过配置一个WebSecurityConfig类开始的,简单代码如下:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and().formLogin(); } }
通过以上代码一个简单的Spring Security应用程序就能成功执行了,该程序能拦截除了/login路径以外的所有请求到登录页面。
我们可以看到以上代码并没有任何显示声明filter的语句,那么Spring Security是如何通过上述代码生成filter的呢?下面就由我来一层层解剖Spring Security的源码来说明。
我们注意到如上代码有个@EnableWebSecurity注解,进入该注解查看
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented #注意这里! @Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class}) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { boolean debug() default false; }WebSecurityConfiguration类
我们可以看到如上该注解导入了WebSecurityConfiguration类,进入该类查看:
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { private WebSecurity webSecurity; private Boolean debugEnabled; private List> webSecurityConfigurers; private ClassLoader beanClassLoader; @Autowired( required = false ) private ObjectPostProcessor
WebSecurityConfiguration类是作为一个Spring配置源,同时定义了许多bean,这里重点看如下这个方法:
@Autowired( required = false ) public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor
总结下该方法所做的主要操作:
首先初始化了webSecurity属性,该属性对应WebSecurity类
注入了webSecurityConfigurers属性,该属性是一个List
遍历webSecurityConfigurers集合,调用webSecurity的apply方法,该方法参数为SecurityConfigurer接口
这里有一个重要的接口SecurityConfigurer接口,该接口代码如下:
public interface SecurityConfigurer> { void init(B var1) throws Exception; void configure(B var1) throws Exception; }
回顾上面我们编写的WebSecurityConfig配置类,也有一个configure方法,那么我们猜测WebSecurityConfig类是不是也实现了SecurityConfigurer接口呢?答案是是的,我们可以看WebSecurityConfig类的类图
可以看到WebSecurityConfig类实现了SecurityConfigurer接口。
因此webSecurityConfigurers属性通过依赖注入包含了WebSecurityConfig类,通过上述第3条操作将我们配置的WebSecurityConfig类和WebSecurity类关联起来。
到这里我们知道了WebSecurityConfiguration类调用上述方法将我们配置的WebSecurityConfig类用WebSecurity类的apply方法关联起来,那么我们详细看看WebSecurity类的apply方法:
public> C apply(C configurer) throws Exception { configurer.addObjectPostProcessor(this.objectPostProcessor); configurer.setBuilder(this); // 继续调用该类的add方法 this.add(configurer); return configurer; } private > void add(C configurer) throws Exception { Assert.notNull(configurer, "configurer cannot be null"); // 获取class属性 Class extends SecurityConfigurer > clazz = configurer.getClass(); // 获取LinkedHashMap LinkedHashMap var3 = this.configurers; synchronized(this.configurers) { if (this.buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } else { List > configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null; if (configs == null) { configs = new ArrayList(1); } ((List)configs).add(configurer); // 将configurer放入一个LinkedHashMap中 this.configurers.put(clazz, configs); if (this.buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } } }
从上述代码可知,实际上就是将WebSecurityConfig类放入了WebSecurity类的一个LinkedHashMap中,该LinkedHashMap在WebSecurity中属性名为configurers。
我们继续回到WebSecurityConfiguration类,查看它的另外一个重要的方法:
@Bean( name = {"springSecurityFilterChain"} ) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() { }); this.webSecurity.apply(adapter); } return (Filter)this.webSecurity.build(); }
该方法即为Spring Security构建Filter的核心方法,通过webSecurity的build方法构建了Spring Security的Filter。
我们继续查看WebSecurity类的build方法:
public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = this.doBuild(); return this.object; } else { throw new AlreadyBuiltException("This object has already been built"); } }
实际上调用了上层的doBuild:
protected final O doBuild() throws Exception { LinkedHashMap var1 = this.configurers; synchronized(this.configurers) { this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING; this.beforeInit(); this.init(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING; this.beforeConfigure(); this.configure(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING; O result = this.performBuild(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT; return result; } }
这里主要看WebSecurity的init方法和performBuild方法,首先看init方法
private void init() throws Exception { // this.getConfigurers()该方法实际上获取WebSecurity中LinkedHashMap中的Value值集合 Collection> configurers = this.getConfigurers(); Iterator var2 = configurers.iterator(); SecurityConfigurer configurer; while(var2.hasNext()) { configurer = (SecurityConfigurer)var2.next(); // 调用SecurityConfigurer的init方法 configurer.init(this); } var2 = this.configurersAddedInInitializing.iterator(); while(var2.hasNext()) { configurer = (SecurityConfigurer)var2.next(); configurer.init(this); } }
通过该代码可知,该方法首先获取WebSecurity中的LinkedHashMap中的Value值集合,再对Value值进行遍历并执行其中的init方法,从上面的代码分析我们知道WebSecurity中的LinkedHashMap实际存的就是WebSecurityConfig,这段代码将会调用WebSecurityConfig的init方法,而WebSecurityConfig的init方法来自于它的父类WebSecurityConfigurerAdapter,该init方法代码如下:
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer{ public void init(final WebSecurity web) throws Exception { // 获取HttpSecurity final HttpSecurity http = this.getHttp(); // 将HttpSecurity放入WebSecurity中 web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } protected final HttpSecurity getHttp() throws Exception { if (this.http != null) { return this.http; } else { DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher()); this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = this.authenticationManager(); this.authenticationBuilder.parentAuthenticationManager(authenticationManager); this.authenticationBuilder.authenticationEventPublisher(eventPublisher); Map , Object> sharedObjects = this.createSharedObjects(); this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects); if (!this.disableDefaults) { ((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.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(); ClassLoader classLoader = this.context.getClassLoader(); List defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); Iterator var6 = defaultHttpConfigurers.iterator(); while(var6.hasNext()) { AbstractHttpConfigurer configurer = (AbstractHttpConfigurer)var6.next(); this.http.apply(configurer); } } // 调用本类的configure方法 this.configure(this.http); return this.http; } } // 模板方法设计模式,子类WebSecurityConfig将会覆盖该方法 protected void configure(HttpSecurity http) throws Exception { this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic(); } }
以上代码最终还是实际调用了我们写的WebSecurityConfig类的configure方法。
仔细观察以上代码,我们发现有一条语句web.addSecurityFilterChainBuilder(http),该语句将构建的HttpSecurity放入WebSecurity类中,以下是该方法源码:
public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder extends SecurityFilterChain> securityFilterChainBuilder) { this.securityFilterChainBuilders.add(securityFilterChainBuilder); return this; }
实际上就是将HttpSecurity放入了WebSecurity的一个list集合里,该list集合属性名为securityFilterChainBuilders。
到目前为止,我们终于知道我们编写的WebSecurityConfig类的configure方法是如何被调用的了,但是仍有许多疑问没解开,比如HttpSecurity类的作用,Spring Security是如何通过HttpSecurity类构建一条拦截器链等。
这里我们先不分析HttpSecurity类的具体实现,再来看看WebSecurity的init方法执行后所执行的performBuild方法,该方法源码如下:
protected Filter performBuild() throws Exception { Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> { return "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 = this.ignoredRequests.size() + this.securityFilterChainBuilders.size(); ListsecurityFilterChains = new ArrayList(chainSize); Iterator var3 = this.ignoredRequests.iterator(); while(var3.hasNext()) { RequestMatcher ignoredRequest = (RequestMatcher)var3.next(); securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0])); } / 遍历securityFilterChainBuilders集合 var3 = this.securityFilterChainBuilders.iterator(); while(var3.hasNext()) { SecurityBuilder extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next(); // 执行securityFilterChainBuilders集合单位的build方法,返回一个SecurityFilterChain类,并加入List 中 securityFilterChains.add(securityFilterChainBuilder.build()); } // 将List 类构建成一个FilterChainProxy代理类 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (this.httpFirewall != null) { filterChainProxy.setFirewall(this.httpFirewall); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (this.debugEnabled) { this.logger.warn(" ******************************************************************** ********** Security debugging is enabled. ************* ********** This may include sensitive information. ************* ********** Do not use in a production system! ************* ******************************************************************** "); result = new DebugFilter(filterChainProxy); } this.postBuildAction.run(); // 返回FilterChainProxy代理类 return (Filter)result; }
该方法执行的操作主要如下:
遍历securityFilterChainBuilders集合,并执行其中的build方法,从上面代码分析可知,securityFilterChainBuilders集合里存储了HttpSecurity,所以这里执行了HttpSecurity的build方法构建SecurityFilterChain类
将List
返回这个FilterChainProxy代理类
到这里总的过程就非常明了了,实际上Spring Security的顶层filter就是一个FilterChainProxy类,而HttpSecurity主要用于注册和实例化各种filter
到这里就分成了两路,一路是HttpSecurity的build方法构建SecurityFilterChain类的原理,一路是FilterChainProxy类的作用,我们先从FilterChainProxy类开始
FilterChainProxy类当请求到达的时候,FilterChainProxy会调用dofilter()方法,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证授权。FilterChainProxy的dofilter()中调用了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); // 获取请求对应的filter列表 Listfilters = 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); // 执行每个filter vfc.doFilter(fwRequest, fwResponse); } // 通过遍历filterChains,调用SecurityFilterChain的matches方法,判断当前的请求对应哪些filter,返回匹配的filter列表 private List getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : filterChains) { if (chain.matches(request)) { return chain.getFilters(); } } return null; }
我们理清了FilterChainProxy类的作用,那么这些SecurityFilterChain是从哪来的呢?从上节可知SecurityFilterChain是由HttpSecurity的build方法生成的,下面我们分析下HttpSecurity类
HttpSecurityHttpSecurity与WebSecurity一样,都继承了AbstractConfiguredSecurityBuilder类,而WebSecurity的build和doBuild方法和LinkedHashMap属性,均来自AbstractConfiguredSecurityBuilder,故HttpSecurity的build方法代码与WebSecurity的相同,区别在于LinkedHashMap存储的东西不同,HttpSecurity正是通过如此来生成SecurityFilterChain类的。
下面我们来看HttpSecurity构建filter的几个常见方法:
public ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry authorizeRequests() throws Exception { ApplicationContext context = this.getContext(); return ((ExpressionUrlAuthorizationConfigurer)this.getOrApply(new ExpressionUrlAuthorizationConfigurer(context))).getRegistry(); } public FormLoginConfigurer formLogin() throws Exception { return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer()); }
都调用了getOrApply方法,再来看getOrApply方法,又调用了其中的apply方法
private> C getOrApply(C configurer) throws Exception { C existingConfig = (SecurityConfigurerAdapter)this.getConfigurer(configurer.getClass()); return existingConfig != null ? existingConfig : this.apply(configurer); } public > C apply(C configurer) throws Exception { configurer.addObjectPostProcessor(this.objectPostProcessor); configurer.setBuilder(this); this.add(configurer); return configurer; }
apply方法又调用了add方法,这里的add方法最终还是将该configurer加入了linkedHashMap中
private> void add(C configurer) throws Exception { Assert.notNull(configurer, "configurer cannot be null"); Class extends SecurityConfigurer > clazz = configurer.getClass(); LinkedHashMap var3 = this.configurers; synchronized(this.configurers) { if (this.buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } else { List > configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null; if (configs == null) { configs = new ArrayList(1); } ((List)configs).add(configurer); this.configurers.put(clazz, configs); if (this.buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } } }
故HttpSecurity在构建filter的过程中,本质还是将形如ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer等类加入了它的LinkedHashMap中。
那么将这些Configurer类存入LinkedHashMap的作用又是什么?我们回忆上面WebSecurity类的doBuild方法,我们知道HttpSecurity类调用的doBuild方法与WebSecurity类一样,而通过观察WebSecurity类doBuild方法里this.init();this.configure();这些语句的具体实现,实际就是调用其LinkedHashMap中的元素的init方法和configure方法。
我们现在来查看其中一个ExpressionUrlAuthorizationConfigurer类的configure方法的详细代码:
public void configure(H http) throws Exception { FilterInvocationSecurityMetadataSource metadataSource = this.createMetadataSource(http); if (metadataSource != null) { FilterSecurityInterceptor securityInterceptor = this.createFilterSecurityInterceptor(http, metadataSource, (AuthenticationManager)http.getSharedObject(AuthenticationManager.class)); if (this.filterSecurityInterceptorOncePerRequest != null) { securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest); } securityInterceptor = (FilterSecurityInterceptor)this.postProcess(securityInterceptor); // 将Filter加入了HttpSecurity的Filters集合中 http.addFilter(securityInterceptor); http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); } }
最后来看看HttpSecruity的performBuild()方法:
protected DefaultSecurityFilterChain performBuild() throws Exception { Collections.sort(this.filters, this.comparator); return new DefaultSecurityFilterChain(this.requestMatcher, this.filters); }
实际上就是通过Filters集合构建了SecurityFilterChain。
从上面代码可总结出,HttpSecurity内部维护一个Filter列表,而HttpSecurity调用形如authorizeRequests(),formLogin()等方法实际上就是将filter添加入它的列表当中,最后通过performBuild()方法构建出SecurityFilterChain,至此HttpSecurity构建filter的总过程就完成了。
总结到目前为止,我们终于知道Spring Security是如何一步步的构建和初始化filter的了,我们最后再来简单总结下构建过程:
Spring Security启动过程中通过WebSecurityConfiguration实例化WebSecurity
WebSecurityConfiguration会将使用者编写的WebSecurityConfig类放入WebSecurity中的LinkedHashMap中
在构建WebSecurity的时候,会调用WebSecurity的doBuild()方法,这个方法是一个核心方法。
doBuild中的init方法将会调用LinkedHashMap中元素的init方法(这里的元素是WebSecurityConfig),然后WebSecurityConfig的init方法会调用configure方法,调用configure方法后,将会初始化HttpSecurity构建各种Filter,这时HttpSecurity将会加入WebSecurity中。
doBuild中的init方法调用完后将会调用下一个performBuild()方法,该方法会获取到HttpSecurity调用其doBuild方法构造SecurityFilterChain
将获取到的SecurityFilterChain构建成一个FilterChainProxy类,作为Spring Security的顶层filter
至此Spring Security的Filter构建完成
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77713.html
摘要:构建完实例后,将它设置为的父认证管理器并将该传入构造器构建实例。,目前为止已经被初始化,接下去需要设置对象添加至的列表中打开类结构,和一样,它也实现了接口,同样继承自。最后返回的是的默认实现。 最近在整合微服务OAuth 2认证过程中,它是基于Spring Security之上,而本人对Spring Security架构原理并不太熟悉,导致很多配置搞不太清楚,遂咬牙啃完了Spring ...
摘要:返回总共需要处理个地方,一个是异常的处理,需要兼容请求,一个是成功返回的处理,一个是失败返回的处理。这里就是拦截,获取提交的参数,然后交给去认证。之后就是走后续的,如果成功,则会进行相应的配置。动态配置权限笔记自定义 序 本文讲述一下如何自定义spring security的登录页,网上给的资料大多过时,而且是基于后端模板技术的,讲的不是太清晰,本文给出一个采用ajax的登录及返回的前...
摘要:的版本增加了对事件监听程序的支持,事件监听程序在建立修改和删除会话或环境时得到通知。元素指出事件监听程序类。过滤器配置将一个名字与一个实现接口的类相关联。 1.简介 web.xml文件是Java web项目中的一个配置文件,主要用于配置欢迎页、Filter、Listener、Servlet等,但并不是必须的,一个java web项目没有web.xml文件照样能跑起来。Tomcat容器/...
阅读 3090·2021-11-22 09:34
阅读 590·2021-11-22 09:34
阅读 2434·2021-10-08 10:18
阅读 3367·2021-09-22 15:57
阅读 2582·2021-09-22 15:25
阅读 2394·2019-08-30 15:54
阅读 2087·2019-08-30 15:44
阅读 1796·2019-08-29 11:18