基于注解的Spring Security原理解析 青旅半醒 2022-06-04 07:09 176阅读 0赞 # 概述: # Spring Security就是引入了一系列的SecurityFilter,将其添加到Spring中去了;在有请求时,根据URL是否符合每个Filter的规则来判断是否需要该Filter来进行处理。 我们先来看下Security的加载过程,在下文将详细说明该加载过程是如何分析出来的: ![这里写图片描述][Center] # 0. 示例: # 示例代码地址:([https://github.com/icarusliu/Test][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][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是否要继续执行。 [Center]: /images/20220604/34be2b00c95648f8b70c1dd6eccf585f.png [https_github.com_icarusliu_Test]: https://github.com/icarusliu/Test [http_localhost]: http://localhost
还没有评论,来说两句吧...