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. MyBatis—mapper.xml映射配置

    SQL文件映射(mapper文件),几个顶级元素的配置: mapper元素:根节点只有一个属性namespace(命名空间)作用: 1:用于区分不同的mapper,全局唯一. 2:绑定DAO接口,即面 ...

  2. Codeforces Round #265 (Div. 2) E

    这题说的是给了数字的字符串 然后有n种的操作没次将一个数字替换成另一个字符串,求出最后形成的字符串的 数字是多大,我们可以逆向的将每个数推出来,计算出他的值和位数记住位数用10的k次方来记 1位就是1 ...

  3. Vue-cli proxyTable 解决开发环境的跨域问题

    Vue-cli proxyTable 解决开发环境的跨域问题 proxyTable: { '/list': { target: 'http://api.xxxxxxxx.com', pathRewri ...

  4. mongo增删改查封装(C#)

    Framework版本:.Net Framework 4 ConnectionUtil源码参见:https://www.cnblogs.com/threadj/p/10536273.html usin ...

  5. Java中com.jcraft.jsch.ChannelSftp讲解

    http://blog.csdn.net/allen_zhao_2012/article/details/7941631 http://www.cnblogs.com/longyg/archive/2 ...

  6. 前端学习笔记之CSS文档流

    先引用一段W3C的文档: 9.3 Positioning schemes In CSS 2.1, a box may be laid out according to three positionin ...

  7. 20145326 《Java程序设计》第10周学习总结

    教材学习内容总结 网络编程 •网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据. •程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据,这个就是狭义的网络编程范畴. •在发送 ...

  8. 自定义Git【转】

    本文转载自:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 自定义Git 在安装G ...

  9. kylin构建cube优化

    前言 下面通过对kylin构建cube流程的分析来介绍cube优化思路. 创建hive中间表 kylin会在cube构建的第一步先构建一张hive的中间表,该表关联了所有的事实表和维度表,也就是一张宽 ...

  10. p4c-bm安装

    Generates the JSON configuration for the behavioral-model (bmv2).它是用来形成 行为模型BMV2 的 JSON配置 的. Importa ...