Spring Security -- Spng Boot中开启Secuty

云平台

  目录 一、开启Spring Security1、导入依赖2、创建Controller3、新建App入口二、基于HTTP basic类型的认证1、配置Spring Security三、Spring Security基本原理1、基本原理2、认证流程3、授权策略四、Spring Security filter的构造和初始化1、@EnableWebSecurity2、WebSecurityConfiguration类3、WebSecurity类4、FilterChainProxy类5、HttpSecurity类6、核心接口SecurityBuilder 与 SecurityConfigurer7、总结五、登录、验证流程分析1、请求/hello接口2、权限验证3、跳转到登陆页面4、开始登录5、认证管理器(AuthenticationManager)进行认证6、权限校验7、权限验证通过六、代码下载在介绍Spring Securiry之前,我们试想一下如果我们自己去实现一个安全框架,我们需要包含哪些功能: 我们需要对登录接口或者一些不需要权限的接口放行,同时我们需要对某些接口进行身份认证,例如:在基于jwt的认证体系中,我们需要校验token是否合法,token合法我们才会放行;我们希望我们写的安全框架能够做一些授权的操作,比如:我们可以限制认证后的用户访问/user接口需要什么权限,访问/group接口又需要什么权限;我们希望安全框架能够提供一个缓存,无论是TreadLocal、还是HttpServletRequest也罢,只要能够获取保存当前认证通过的用户信息即可;试想一下,如果我们去实现这些功能,我们需要怎么做: 我们需要去拦截所有的HTTP请求,我们首先想到的实现方式就是filter、Spring AOP、Intercepter,这三者的实现方式和应用场景都不一样,这里我们不去细究采用哪种方式,但是我可以告诉你Spring Security是采用了一系列的filter实现的。假设我们也是采用的filter实现,那么我们是不是也要实现一个白名单啊,比如放行/login接口啊,然后剩下的接口,就要走认证流程;认证完之后,我们怎么做授权呢,我们可以这么做,我们先获取当前登录用户所拥有的权限,比如某某用户对接口资源user具有添加权限,采用这种格式:interface:user:add,我们将若干个这种格式的权限放到一个list,然后放到缓存中[ "interface:user:add", "nterface:group:delete", ...]然后我们干一件什么事呢,我们搞个@PreAuthorize注解,然后在搞个注解处理器,注解处理器可以使用Spring AOP去实现。这个注解怎么用呢,我们可以将这个注解加载UserController /user/add接口上: @PostMapping("/user/add") @PreAuthorize("interface:user:add") public NgspResponseEntity<User> insertUser(...)如果用户去访问/user/add接口,我们就去缓存中拉取用户的权限列表,然后去校验用户是否具有访问这个接口的权限,如果有那么我们就放行。当然了上面只是一个简单的实现,Spring Security的实现那是太太太复杂了,他为了满足各种需求,允许我们自己去配置各种过滤器,功能是强大了,但是学习起来还是比较困难的。 下面我将带领大家来学习Spring Security框架,Spring Security是一款基于Spring的安全框架,主要包含认证和授权两大安全模块,和另外一款流行的安全框架Apache Shiro相比,它拥有更为强大的功能。Spring Security也可以轻松的自定义扩展以满足各种需求,并且对常见的Web安全攻击提供了防护。如果你的Web框架选择的是Spring,那么在安全方面Spring Security会是一个不错的选择。 回到顶部 一、开启Spring Security 1、导入依赖 创建一个Spring Boot项目springboot-springsecurity,然后引入spring-boot-starter-security: <!-- Spring Security的maven依赖 --> <depency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </depency>2、创建Controller 新建包com.goldwind.controller,接下来我们创建一个TestController,对外提供一个/hello服务: package com.goldwind.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;/** * @Author: zy * @Description: 测试 * @Date: 2020-2-9 */@RestControllerpublic class TestController { @GetMapping("hello") public String hello() { return "hello spring security"; }}3、新建App入口 新建入口程序App.java: package com.goldwind;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @Author: zy * @Description: 启动程序 * @Date: 2020-2-9 */@SpringBootApplicationpublic class App { public static void main(String[] args){ SpringApplication.run(App.class,args); }}这时候我们直接启动项目,访问http://localhost:8080/hello,可看到页面弹出了个formLogin认证框: 这个配置开启了一个form Login类型的认证,所有服务的访问都必须先过这个认证,默认的用户名为user,密码由Sping Security自动生成,回到IDE的控制台,可以找到密码信息: Using generated security password: a77c9456-901e-4848-a221-3822347e52ea输入用户名user,密码a77c9456-901e-4848-a221-3822347e52ea后,我们便可以成功访问/hello接口。 回到顶部 二、基于HTTP basic类型的认证 我们可以通过一些配置将表单认证修改为基于HTTP Basic的认证方式。 1、配置Spring Security 创建包com.goldwind.config,创建一个配置类WebSecurityConfig继承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter这个抽象类并重写configure(HttpSecurity http)方法。WebSecurityConfigurerAdapter是由Spring Security提供的Web应用安全配置的适配器: package com.goldwind.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/** * @Author: zy * @Description: spring security配置类 * @Date: 2020-2-9 */@Configurationpublic class WebSecurityConfig exts WebSecurityConfigurerAdapter { /** * 配置拦截请求资源 * @param http:HTTP请求安全处理 * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() //HTTP Basic认证方式 .and() .authorizeRequests() // 授权配置 .anyRequest() // 所有请求 .authenticated(); // 都需要认证 }}Spring Security提供了这种链式的方法调用。上面配置指定了认证方式为HTTP Basic登录,并且所有请求都需要进行认证。 这里有一点需要注意,我没并没有在Spring Security配置类上使用@EnableWebSecurity注解。这是因为在非Spring Boot的Spring Web MVC应用中,注解@EnableWebSecurity需要开发人员自己引入以启用Web安全。而在基于Spring Boot的Spring Web MVC应用中,开发人员没有必要再次引用该注解,Spring Boot的自动配置机制WebSecurityEnablerConfiguration已经引入了该注解,如下所示: package org.springframework.boot.autoconfigure.security.servlet;// 省略 imports 行@Configuration// 仅在存在 WebSecurityConfigurerAdapter bean 时该注解才有可能生效// (最终生效与否要结合其他条件综合考虑)@ConditionalOnBean(WebSecurityConfigurerAdapter.class)// 仅在不存在 springSecurityFilterChain 时该注解才有可能生效// (最终生效与否要结合其他条件综合考虑)@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)// 仅在 Servlet 环境下该注解才有可能生效// (最终生效与否要结合其他条件综合考虑)@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)@EnableWebSecurity // <====== 这里启用了 Web 安全public class WebSecurityEnablerConfiguration {}实际上,一个Spring Web应用中,WebSecurityConfigurerAdapter可能有多个 , @EnableWebSecurity可以不用在任何一个WebSecurityConfigurerAdapter上,可以用在每个WebSecurityConfigurerAdapter上,也可以只用在某一个WebSecurityConfigurerAdapter上。多处使用@EnableWebSecurity注解并不会导致问题,其最终运行时效果跟使用@EnableWebSecurity一次效果是一样的。 这时候我们重启项目,再次访问http://localhost:8080/hello,可以看到认证方式已经是HTTP Basic的方式了: 用户名依旧是user,密码由Spring Security自动生成,如果需要换回表单的认证方式,我们只需要简单修改configure方法中的配置: @Overrideprotected void configure(HttpSecurity http) throws Exception { // http.formLogin() // 表单方式 http.httpBasic() // HTTP Basic方式 .and() .authorizeRequests() // 授权配置 .anyRequest() // 所有请求 .authenticated(); // 都需要认证}回到顶部 三、Spring Security基本原理 上面我们开启了一个最简单的Spring Security安全配置,下面我们来了解下Spring Security的基本原理。通过上面的的配置。 1、基本原理 Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。而Spring Security对Web资源的保护是靠Filter实现的。当初始化Spring Security时,WebSecurityConfiguration会创建一个名为 springSecurityFilterChain 的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类。 FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,下图为实际调试中创建的FilterChainProxy实例。 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。 Spring Security功能的实现主要是由一系列过滤器链相互配合完成,如下图(只是挑选了一些重要的Filter进行讲解): 下面介绍几个重要的过滤器: UsernamePasswordAuthenticationFilter过滤器用于处理基于表单方式的登录验证,该过滤器默认只有当请求方法为post、请求页面为/login时过滤器才生效,如果想修改默认拦截url,只需在刚才介绍的Spring Security配置类WebSecurityConfig中配置该过滤器的拦截url:.loginProcessingUrl("url")即可;BasicAuthenticationFilter用于处理基于HTTP Basic方式的登录验证,当通过HTTP Basic方式登录时,默认会发送post请求/login,并且在请求头携带Authorization:Basic dXNlcjoxOWEyYWIzOC1kMjBiLTQ0MTQtOTNlOC03OThkNjc2ZTZlZDM=信息,该信息是登录用户名、密码加密后的信息,然后由BasicAuthenticationFilter过滤器解析后,构建UsernamePasswordAuthenticationFilter过滤器进行认证;如果请求头没有Authorization信息,BasicAuthenticationFilter过滤器则直接放行;FilterSecurityInterceptor的拦截器,用于判断当前请求身份认证是否成功,是否有相应的权限,当身份认证失败或者权限不足的时候便会抛出相应的异常;ExceptionTranslateFilter捕获并处理,所以我们在ExceptionTranslateFilter过滤器用于处理了FilterSecurityInterceptor抛出的异常并进行处理,比如需要身份认证时将请求重定向到相应的认证页面,当认证失败或者权限不足时返回相应的提示信息;2、认证流程 以表单方式登录验证为例,认证流程如下: 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。其中web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。3、授权策略 Spring Security可以通过 http.authorizeRequests() 对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。授权流程如下: 回到顶部 四、Spring Security filter的构造和初始化 我们已经知道Spring Security通过构造层层filter来实现登录跳转、权限验证,角色管理等功能。这里我们将通过剖析Spring Security的核心源码来说明Spring Security的filter是如何开始构造的,下面的讲解比较长,如果你不是特别感兴趣,可以直接跳到总结,总结粗略了叙述了Spring Security框架filters的构建过程。我们可以下载Spring Security源码; 1、@EnableWebSecurity 我们知道要想启动Spring Security,必须配置注解@EnableWebSecurity,我们就从该注解说起: @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)@Target(value = { java.lang.annotation.ElementType.TYPE })@Documented@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class })@EnableGlobalAuthentication@Configurationpublic @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false;}2、WebSecurityConfiguration类 我们可以看到该注解导入了WebSecurityConfiguration类,进入该类查看: View Code 如果我们忽略掉细节,只看最重要的步骤,该类主要实现了如下功能: WebSecurityConfiguration类是作为一个Spring配置源,同时定义了许多bean,这里重点看WebSecurityConfiguration#setFilterChainProxySecurityConfigurer这个方法: /** * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} * instances used to create the web configuration. * * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a * {@link WebSecurity} instance * @param webSecurityConfigurers the * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to * create the web configuration * @throws Exception */ @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); } 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; } for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { webSecurity.apply(webSecurityConfigurer); } this.webSecurityConfigurers = webSecurityConfigurers; }下面我们对每一个步骤来做相应的源代码解释,首先我们来看一下方法得第二个参数: @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}"可以看一下autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()的源代码: 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; }我们如果调试代码,可以发现beanFactory.getBeansOfType从Spring容器获取类型为WebSecurityConfigurer的bean,在这里也就是获取到我们编写的WebSecurityConfig配置类: 我们可以看一下WebSecurityConfig类的类图: 然后直接用new 来创建一个WebSecurity的对象,用来初始化了WebSecurityConfiguration#webSecurity属性; webSecurity = objectPostProcessor .postProcess(new WebSecurity(objectPostProcessor)); if (debugEnabled != null) { webSecurity.debug(debugEnabled); }当有多个配置项时进行排序,进行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; }遍历WebSecurityConfiguration#webSecurityConfigurers集合,调用webSecurity的apply方法,此时也会将我们自定义的WebSecurityConfig应用到 webSecurity.apply方法上: for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { webSecurity.apply(webSecurityConfigurer); }最后,初始化WebSecurityConfiguration#webSecurityConfigurers属性: this.webSecurityConfigurers = webSecurityConfigurers;3、WebSecurity类 到这里我们知道了WebSecurityConfiguration类调用上述方法将我们配置的WebSecurityConfig类用WebSecurity类的apply方法关联起来,那么我们详细看看WebSecurity类的apply方法: public <C exts SecurityConfigurer<O, B>> C apply(C configurer) throws Exception { add(configurer); return configurer; } private <C exts SecurityConfigurer<O, B>> void add(C configurer) { Assert.notNull(configurer, "configurer cannot be null"); Class<? exts SecurityConfigurer<O, B>> clazz = (Class<? exts SecurityConfigurer<O, B>>) configurer .getClass(); synchronized (configurers) { if (buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers .get(clazz) : null; if (configs == null) { configs = new ArrayList<>(1); } configs.add(configurer); this.configurers.put(clazz, configs); if (buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } }从上述代码可知,实际上就是将我们定义的WebSecurityConfig配置类放入了WebSecurity类的一个LinkedHashMap中: 该LinkedHashMap在WebSecurity中属性名为configurers: private final LinkedHashMap<Class<? exts SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>(); 可以看到键就是我们定义的配置类的Clazz类型,而值是List<SecurityConfigurer<Filter, WebSecurity>>类型,是一个list集合,其中只有一个元素,就是我们编写的WebSecurityConfig配置类; 我们继续回到WebSecurityConfiguration类,查看它的另外一个重要的方法: build的方法来自于WebSecurity的父类AbstractSecurityBuilder,该方法即为Spring Security构建Filter的核心方法,通过webSecurity的build方法构建了Spring Security的Filter: 实际上调用了父类AbstractConfiguredSecurityBuilder的doBuild: 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方法和WebSecurity实现的performBuild方法,首先看init方法,init方法将会遍历WebSecurity的LinkHashMap configurers中每个元素configurer,执行以下步骤: 调用configurer的init方法,init该方法来自父类WebSecurityConfigurerAdapter(这里的this指定就是WebSecurity): init方法中又会调用WebSecurityConfigurerAdapter#getHttp方法: getHttp方法首先创建一个HttpSecurity对象,用来初始化configurer的http成员: private HttpSecurity http;又调用 configure 方法,最终将会执行我们在WebSecurityConfig写的configure方法: configurer中的init方法执行完之后,WebSecurity调用addSecurityFilterChainBuilder方法将configurer创建的HttpSecurity放入了WebSecurity的一个list集合里,该list集合属性名为securityFilterChainBuilders: public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? exts SecurityFilterChain> securityFilterChainBuilder) { this.securityFilterChainBuilders.add(securityFilterChainBuilder); return this; } 到目前为止,我们终于知道我们编写的WebSecurityConfig类的configure方法是如何被调用的了,但是仍有许多疑问没解开,比如HttpSecurity类的作用,Spring Security是如何通过HttpSecurity类构建一条拦截器链等。 这里我们先不分析HttpSecurity类的具体实现,再来看看WebSecurity的init方法执行后所执行的performBuild方法,该方法源码如下: @Override protected Filter performBuild() throws Exception { Assert.state( !securityFilterChainBuilders.isEmpty(), () -> "At least one SecurityBuilder<? exts SecurityFilterChain> needs to be specified. " + "Typically this done by adding a @Configuration that exts 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<? exts 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; }该方法执行的操作主要如下: (1)、遍历WebSecurity的securityFilterChainBuilders列表,一般也就一个元素,也就是我们的WebSecurityConfig配置类创建的HttpSecurity对象,并执行该对象的build方法,初始化成员属性filters,并通过filters集合构建SecurityFilterChain类; 然后将每个HttpSecurity对象构建的SecurityFilterChain对象添加到securityFilterChains列表中。 (2)、将List<SecurityFilterChain>集合构建成一个FilterChainProxy代理类,返回这个FilterChainProxy代理类; 到这里总的过程就非常明了,WebSecurityConfiguration的springSecurityFilterChain方法最终返回了一个FilterChainProxy代理类,作为Spring Security的顶层filter,而HttpSecurity主要用于注册和实例化各种filter,HttpSecurity类有个属性名为filters的List列表专门用于保存过滤器的。 到这里就分成了两路: 一路是HttpSecurity的build方法构建SecurityFilterChain类的原理;一路是FilterChainProxy类的作用;FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);我们先从FilterChainProxy类开始。 4、FilterChainProxy类 当请求到达的时候,FilterChainProxy会调用dofilter方法,会遍历所有的SecurityFilterChain,对匹配到的url,则调用SecurityFilterChain中的每一个filter做认证授权。FilterChainProxy的dofilter()中调用了doFilterInternal方法,如下: private List<SecurityFilterChain> filterChains; @Override 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); } } 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); }这里我就不贴VirtualFilterChain的源码了,实际上就是先去遍历执行我们filters中的过滤器的doFilter,最后再去执行chiin.doFilter。 我们理清了FilterChainProxy类的作用,那么这些SecurityFilterChain是从哪来的呢?从上节可知SecurityFilterChain是由HttpSecurity的build方法生成的,下面我们分析下HttpSecurity类。 5、HttpSecurity类 HttpSecurity与WebSecurity一样,都继承了AbstractConfiguredSecurityBuilder类,而WebSecurity的build和doBuild方法和LinkedHashMap属性,均来自AbstractConfiguredSecurityBuilder,故HttpSecurity的build方法代码与WebSecurity的相同,区别在于LinkedHashMap存储的东西不同: (1)、WebSecurityConfig类的configure方法 在之前我们已经介绍了WebSecurity的doBuild是如何调用我们自己写的配置类WebSecurityConfig的configure方法;在该方法中http所调用的方法,最终的结果就是产生url-filter的关系映射: protected void configure(HttpSecurity http) throws Exception { http.formLogin() //HTTP Basic认证方式 .and() .authorizeRequests() // 授权配置 .anyRequest() // 所有请求 .authenticated(); // 都需要认证 }比如authorizeRequests(),formLogin()方法分别返回ExpressionUrlAuthorizationConfigurer和FormLoginConfigurer,他们都是SecurityConfigurer接口的实现类,分别代表的是不同类型的安全配置器。而这些安全配置器分别对应一个或多个filter: formLogin对应UsernamePasswordAuthenticationFilter,此外我们还可以给安全过滤器FormLoginConfigurer指定其它参数,比如.login("/login"):自定义登录请求页面;.loginProcessingUrl("/login")指定UsernamePasswordAuthenticationFilter拦截的Form action;.and() .formLogin() // 或者httpBasic() .loginPage("/login") // 指定登录页的路径 .loginProcessingUrl("/login") // 指定自定义form表单提交请求的路径authorizeRequests对应FilterSecurityInterceptor; public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests() throws Exception { ApplicationContext context = getContext(); return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)) .getRegistry(); }public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception { return getOrApply(new FormLoginConfigurer<>()); }都调用了getOrApply方法,再来看getOrApply方。

标签: 云平台