基于注解的Spring Security原理解析
来源:互联网 发布:ant java 参数详解 编辑:程序博客网 时间:2024/05/29 06:44
概述:
Spring Security就是引入了一系列的SecurityFilter,将其添加到Spring中去了;在有请求时,根据URL是否符合每个Filter的规则来判断是否需要该Filter来进行处理。
我们先来看下Security的加载过程,在下文将详细说明该加载过程是如何分析出来的:
0. 示例:
示例代码地址:(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即可看到首页,可以点击用户管理等后台管理模块会要求进行登录; 使用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@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;}
其重点就是@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是否要继续执行。
- 基于注解的Spring Security原理解析
- 基于注解配置的spring mvc 4 + spring security 4实例与解析
- spring注解原理解析
- Spring基于注解形式的 AOP的原理流程及源码解析(一)
- Spring基于注解形式的 AOP的原理流程及源码解析(二)
- Spring基于注解形式的 AOP的原理流程及源码解析(三)
- Spring基于注解形式的 AOP的原理流程及源码解析(四)
- Spring Security 注解备忘
- spring security 注解@EnableGlobalMethodSecurity的三种开启注解方式
- 基于注解的SpringMVC原理
- spring 依赖注入注解配置原理解析
- Spring+junit4 实现注解测试原理解析。
- Spring+junit4 实现注解测试原理解析
- 基于注释的Spring Security实战指南
- spring security基于aop的方法拦截
- 基于注释的Spring Security实战指南
- spring security:基于MongoDB的认证
- 基于spring security的用户单点登录
- mysql Parameter index out of range (2 > number of parameters, which is 1).
- QT5获取本机信息
- Oracle连接数过多释放机制
- poj1651 Multiplication Puzzle(区间dp)
- Python3 pandas read_csv 读取txt文件报错:IOError: Initializing from file failed
- 基于注解的Spring Security原理解析
- [Leetcode]Graph & Union Find
- Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十一)redis密码设置、安全设置
- # C#调用已经使用Python训练好的神经网络做图片检测
- Bandwagon的vps设置vpn代理
- 将linux(ubuntu)安装到U盘下面--便携式ubuntu和使用dd制作U盘安装工具
- 技术分享连载(八十)
- C/C++ 第八周串和数组 (一)顺序串算法 项目2—(一)
- 算符优先系列之(一)Firstvt和Lastvt集