spring security是通过一个过滤器链来保护你的web应用安全。在spring security中,该过滤链的名称为springSecurityFilterChain,类型为FilterChainProxy。并通过DelegatingFilterProxy代理调用。对于这一点,这样说可能更好理解:springSecurityFilterChain是spring中的bean,而过滤器要想起作用必须配置在web.xml中。为了使springSecurityFilterChain起到拦截作用,就必须让web.xml意识到(其实应该是说让servlet容器/Tomcat)。而DelegatingFilterChain就起到了该角色的作用。它将web.xml和springSecurityFilterChain联系起来。接下来,我们用spring-security-4 (2)spring security 基于Java配置的搭建中的代码为例,来讲解spring security过滤器的创建和注册原理。

一、Spring Security过滤器的创建原理

  让我们首先看下MySecurityConfig类

@EnableWebSecurity
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
public void configUser(AuthenticationManagerBuilder builder) throws Exception {
builder
.inMemoryAuthentication()
//创建用户名为user,密码为password的用户
.withUser("user").password("password").roles("USER");
}
}

  可以看到MySecurityConfig上的@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 { /**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}

  @EnableWebSecurity上的@Import注解引入了两个类WebSecurityConfiguration和SpringWebMvcImportSelector,spring security的过滤器正是由WebSecurityConfiguration创建。让我们看下WebSecurityConfiguration的部分源码

...
//查看AbstractSecurityWebApplicationInitializer的源码可以看到
//AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME = "springSecurityFilterChain"
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
//如果没有配置类那么就new一个WebSecurityConfigurerAdapter,也就是说我们没有配置MySecurityConfig或者说其没有被spring扫描到
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
//创建Filter
return webSecurity.build();
}
...

  从源码中可以看到通过WebSecurity.build()创建出名字为springSecurityFilterChain的Filter对象。(特别说明一下,一定要保证我们的MySecurityConfig类注解了@Configuration并可以被spring扫描到,如果没有被sping扫描到,那么spring security会认为没有配置类,就会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。)那么该Filter的类型是什么呢?别着急,我们先来看下WeSecurity的继承体系。

  

  build方法定义在AbstractSecurityBuilder中,源码如下:

...
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
//通过doBuild方法创建
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
...

  doBuild方法定义在AbstractConfiguredSecurityBuilder中,源码如下:

...
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING; beforeInit();
init(); buildState = BuildState.CONFIGURING; beforeConfigure();
configure(); buildState = BuildState.BUILDING; //performBuild方法创建
O result = performBuild(); buildState = BuildState.BUILT; return result;
}
}
...

  performBuild()方法定义在WebSecurity中,源码如下

...
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 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;
}
...

  不关心其具体实现,我们从源码中看到spring security创建的过滤器类型为FilterChainProxy。由此完成过滤器的创建。

二、Spring Security过滤器的注册原理

  看下我们创建的SecurityInitializer类:

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

  这段代码虽然很简单,但却是注册过滤器所必须的。

  根据Servlet3.0中,提供了ServletContainerInitializer接口,该接口提供了一个onStartup方法,用于在容器启动时动态注册Servlet,Filter,Listener等。因为我们建立的是web项目,那我们的依赖中肯定是由spring-web依赖的

  根据Servlet 3.0规范,Servlet容器在启动时,会负责创建图中红色箭头所指的类,即SpringServletContainerInitializer,该类是ServletContainerInitializer的实现类。那么该类必有onStartup方法。让我们看下它的源码

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
//如果waiClass不为接口,抽象类,并且属于WebApplicationInitializer类型
//那么通过反射构造该接口的实例。
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
} if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
} AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers); for (WebApplicationInitializer initializer : initializers) {
//调用所有WebApplicationInitializer实例的onStartup方法
initializer.onStartup(servletContext);
}
} }

  请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解,根据Sevlet3.0规范,Servlet容器要负责以Set集合的方式注入指定类的子类(包括接口,抽象类)。其中AbstractSecurityWebApplicationInitializer是WebApplicationInitializer的抽象子类,我我们看下它的onStartup方法

...
public final void onStartup(ServletContext servletContext) throws ServletException {
beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (enableHttpSessionEventPublisher()) {
servletContext.addListener(
"org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(getSessionTrackingModes());
//注册过滤器
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
...

  该类中的insertSpringSecurityFilterChain(servletContext)就是在注册过滤器。因为在过滤器创建中所说的springSecurityFilterChain,它其实是spring中的bean,而servletContext也必定可以获取到该bean。我们接着看insertSpringSecurityFilterChain的源码

...
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; private void insertSpringSecurityFilterChain(ServletContext servletContext) { String filterName = DEFAULT_FILTER_NAME;
//通过DelegatingFilterProxy代理
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
//完成过滤器的注册
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
...

  一开始我们就提到了调用过滤器链springSecurityFilterChain需要DelegatingFilterProxy进行代理,将其与web.xml联系起来。这段代码就是很好的证明。DelegatingFilterProxy中维护了一个类型为String,名字叫做targetBeanName的字段,targetBeanName就是DelegatingFilterProxy所代理的类的名称。最后通过registerFilter最终完成过滤器的注册。

参考资料:http://www.tianshouzhi.com/api/tutorials/spring_security_4/250

     https://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/

spring-security-4 (3)spring security过滤器的创建与注册原理的更多相关文章

  1. Spring Security教程(五):自定义过滤器从数据库从获取资源信息

    在之前的几篇security教程中,资源和所对应的权限都是在xml中进行配置的,也就在http标签中配置intercept-url,试想要是配置的对象不多,那还好,但是平常实际开发中都往往是非常多的资 ...

  2. Spring MVC 项目搭建 -6- spring security 使用自定义Filter实现验证扩展资源验证,使用数据库进行配置

    Spring MVC 项目搭建 -6- spring security使用自定义Filter实现验证扩展url验证,使用数据库进行配置 实现的主要流程 1.创建一个Filter 继承 Abstract ...

  3. Spring Boot整合实战Spring Security JWT权限鉴权系统

    目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.像以前做项目的安全认证基于 session 的登录拦截,属于后端全栈式的开发的模式 ...

  4. Spring Boot:整合Spring Security

    综合概述 Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication)和授权(Author ...

  5. spring 的权限控制:security

    下面我们将实现关于Spring Security3的一系列教程. 最终的目标是整合Spring Security + Spring3MVC 完成类似于SpringSide3中mini-web的功能. ...

  6. Spring Security 解析(七) —— Spring Security Oauth2 源码解析

    Spring Security 解析(七) -- Spring Security Oauth2 源码解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...

  7. 基于spring的安全管理框架-Spring Security

    什么是spring security? spring security是基于spring的安全框架.它提供全面的安全性解决方案,同时在Web请求级别和调用级别确认和授权.在Spring Framewo ...

  8. Spring Cloud 学习 (九) Spring Security, OAuth2

    Spring Security Spring Security 是 Spring Resource 社区的一个安全组件.在安全方面,有两个主要的领域,一是"认证",即你是谁:二是& ...

  9. Spring MVC 项目搭建 -5- spring security 使用数据库进行验证

    Spring MVC 项目搭建 -5- spring security 使用数据库进行验证 1.创建数据表格(这里使用的是mysql) CREATE TABLE security_role ( id ...

随机推荐

  1. 高级Bash脚本编程(二)

    高级Bash脚本编程(二) 退出 退出状态码 退出:exit 被用来结束一个脚本,它也返回一个值,并且这个值会传递给脚本的父进程,父进程会使用这个值做下一步的处理. 每个命令都会返回一个退出状态码,成 ...

  2. Linux服务器使用tar加密压缩文件

    使用tar命令进行加密压缩时需要和OpenSSL进行结合 加密压缩命令如下: tar -zcf - filename |openssl des3 -salt -k passwd | dd of=fil ...

  3. CodeForces 838A Binary Blocks(前缀和)题解

    题意:给你个n*m的矩阵,要求你找到一个k,k > 1,使得矩阵可以分为很多k * k的小正方形,然后进行操作把每个小正方形都变为0或1,问你怎样使操作数最小. 思路:随便暴力不可取,显然你每次 ...

  4. in和exists

    exists和in的使用方式: #对B查询涉及id,使用索引,故B表效率高,可用大表 -->外小内大 select * from A where exists (select * from B ...

  5. HDU 2732 Leapin' Lizards(最大流)

    http://acm.hdu.edu.cn/showproblem.php?pid=2732 题意: 给出n行的网格,还有若干只蜥蜴,每只蜥蜴一开始就在一个格子之中,并且给出蜥蜴每次的最大跳跃长度d. ...

  6. Combobox绑定泛型字典时提示“复杂的 DataBinding 接受 IList 或 IListSource 作为数据源”的解决方法

    一般情况下我们会将 DataTable 或 DataView 绑定到 Combobox 控件上,这时候进行数据绑定是没有问题的,因为DataTable 和 DataView 都继承了 IList 接口 ...

  7. linux中find与rm实现查找并删除目录或文件

    linux 下用find命令查找文件,rm命令删除文件. 删除指定目录下指定文件find 要查找的目录名 -name .svn |xargs rm -rf 删除指定名称的文件或文件夹: find -t ...

  8. python 列表求和

    def sum_list(items): sum_numbers = for x in items: sum_numbers += x return sum_numbers print(sum_lis ...

  9. maven3常用命令、java项目搭建、web项目搭建

    ------------------------------maven3常用命令--------------------------- 1.常用命令 1)创建一个Project mvn archety ...

  10. 【转】IntelliJ IDEA的光芒会盖过Eclipse吗

    作为一个资深的Eclipse用户,我想对IntelliJ IDEA做一个更为严谨的审视.JetBrains的工作人员非常的友善,并为Podcastpedia.org和Codingpedia.org这两 ...