请稍等ManixChen正在解析过程中………



spring security 探秘



概述

Spring Security这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。

本文的宗旨并非描述如何从零开始搭建一个 “hello world” 级的demo,或者列举有哪些可配置项(这种类似于词典的文档,没有比参考书更合适的了),而是简单描述spring-security项目的整体结构,设计思想,以及某些重要配置做了什么。

本文所有内容基于spring-security-4.0.1.RELEASE ,你可以在Github中找到它,或者使用Maven获取,引入spring-security-config是为了通过命名空间简化配置。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>

Filter

spring-security的业务流程是独立于项目的,我们需要在web.xml中指定其入口,注意该过滤器必须在项目的过滤器之前。

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <servlet-name>/*</servlet-name>
</filter-mapping>

值得一提的是,该过滤器的名字具有特殊意义,没有特别需求不建议修改,我们可以在该过滤的源码中看到,其过滤行为委托给了一个delegate对象,该delegate对象是一个从spring容器中获取的bean,依据的beanid就是filter-name。

@Override
protected void initFilterBean() throws ServletException {
	synchronized (this.delegateMonitor) {
		if (this.delegate == null) {

			if (this.targetBeanName == null) {
				this.targetBeanName = getFilterName();
			}

			WebApplicationContext wac = findWebApplicationContext();
			if (wac != null) {
				this.delegate = initDelegate(wac);
			}
		}
	}
}

HTTP

我们可以在security中声明多个http元素,每个http元素将产生一个FilterChain,这些FilterChain将按照声明顺序加入到FilterChainProxy中,而这个FilterChainProxy就是web.xml中定义的springSecurityFilterChain内部的delegate

<security:http security="none" pattern="/favicon.ico" />
<security:http security="none" pattern="/resources/**" />
<security:http security="none" pattern="/user/login" />

在http元素也就是FilterChain中,以责任链的形式存在多个Filter,这些Filter真正执行过滤操作,http标签中的许多配置项,如 <security:http-basic/><security:logout/>等,其实就是创建指定的Filter,以下表格列举了这些Filter。

filter

利用别名,我们可以将自定义的过滤器加入指定的位置,或者替换其中的某个过滤器。

<security:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />

整体来看,一个FilterChainProxy中可以包含有多个FilterChain,一个FilterChain中又可以包含有多个Filter,然而对于一个既定请求,只会使用其中一个FilterChain。

FilterChain

filterChain

上图列举了一些Filter, 此处将说明这些Filter的作用, 在需要插入自定义Filter时, 这些说明可以作为参考。

  • SecurityContextPersistenceFilter 创建一个空的SecurityContext(如果session中没有SecurityContext实例),然后持久化到session中。在filter原路返回时,还需要保存这个SecurityContext实例到session中。

  • RequestCacheAwareFilter 用于用户登录成功后,重新恢复因为登录被打断的请求

  • AnonymousAuthenticationFilter 如果之前的过滤器都没有认证成功,则为当前的SecurityContext中添加一个经过匿名认证的token, 所有与认证相关的过滤器(如CasAuthenticationFilter)都应当放在AnonymousAuthenticationFilter之前。

  • SessionManagementFilter 1.session固化保护-通过session-fixation-protection配置 2.session并发控制-通过concurrency-control配置

  • ExceptionTranslationFilter 主要拦截两类安全异常:认证异常、访问拒绝异常。而且仅仅是捕获后面的过滤器产生的异常。所以在自定义拦截器时,需要注意在链中的顺序。

  • FilterSecurityInterceptor 通过决策管理器、认证管理器、安全元数据来判断用户是否能够访问资源。

FilterSecurityInterceptor

如果一个http请求能够匹配security定义的规则,那么该请求将进入security处理流程,大体上,security分为三个部分:

  • AuthenticationManager 处理认证请求
  • AccessDecisionManager 提供访问决策
  • SecurityMetadataSource 元数据

以下代码摘自AbstractSecurityInterceptor, 这是FilterSecurityInterceptor的父类, 也正是在此处区分了web请求拦截器与方法调用拦截器。(代码有所精简)

protected InterceptorStatusToken beforeInvocation(Object object) {

	if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
		throw new IllegalArgumentException();
	}

	Collection<ConfigAttribute> attributes =
	        this.obtainSecurityMetadataSource().getAttributes(object);

	if (attributes == null || attributes.isEmpty()) {
		if (rejectPublicInvocations) {
			throw new IllegalArgumentException();
		}
		publishEvent(new PublicInvocationEvent(object));
		return null; // no further work post-invocation
	}

	if (SecurityContextHolder.getContext().getAuthentication() == null) {
	    //...
	}

	Authentication authenticated = authenticateIfRequired();

	// Attempt authorization
	try {
		this.accessDecisionManager.decide(authenticated, object, attributes);
	}
	catch (AccessDeniedException accessDeniedException) {
		publishEvent(new AuthorizationFailureEvent(object, attributes,
		            authenticated,accessDeniedException));
		throw accessDeniedException;
	}
}


private Authentication authenticateIfRequired() {
    Authentication authentication = SecurityContextHolder.getContext()
            .getAuthentication();

    if (authentication.isAuthenticated() && !alwaysReauthenticate) {
        return authentication;
    }

    authentication = authenticationManager.authenticate(authentication);

    SecurityContextHolder.getContext().setAuthentication(authentication);

    return authentication;
}

在FilterSecurityInterceptor的处理流程中,首先会处理认证请求,获取用户信息,然后决策处理器根据用户信息与权限元数据进行决策,同样,这三个部分都是可以自定义的。

<!-- 自定义过滤器 -->
<bean id="filterSecurityInterceptor"
            class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="securityMetadataSource" ref="securityMetadataSource"/>
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager" ref="accessDecisionManager"/>
</bean>

AuthenticationManager

AuthenticationManager处理认证请求,然而它并不直接处理,而是将工作委托给了一个ProviderManager,ProviderManager又将工作委托给了一个AuthenticationProvider列表,只要任何一个AuthenticationProvider认证通过,则AuthenticationManager认证通过,我们可以配置一个或者多个AuthenticationProvider,还可以对密码进行加密。

<security:authentication-manager id="authenticationManager">
    <security:authentication-provider user-service-ref="userDetailsService" >
        <security:password-encoder base64="true" hash="md5">
            <security:salt-source user-property="username"/>
        </security:password-encoder>
    </security:authentication-provider>
</security:authentication-manager>

考虑到一种常见情形,用户输入用户名密码,然后与数据比对,验证用户信息,security提供了类来处理。

<bean id="userDetailsService"
            class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl" >
     <property name="dataSource" ref="dataSource"/>
</bean>

JdbcDaoImpl使用内置的SQL查询数据,这些SQL以常量的形式出现在JdbcDaoImpl开头,同样可以注入修改。

AccessDecisionManager

AccessDecisionManager提供访问决策,它同样不会直接处理,而是仅仅抽象为一种投票规则,然后决策行为委托给所有投票人。

<!-- 决策管理器 -->
<bean id="accessDecisionManager"
            class="org.springframework.security.access.vote.AffirmativeBased" >
    <property name="allowIfAllAbstainDecisions" value="false"/>
    <constructor-arg index="0">
        <list>
           <!-- <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>-->
            <bean class="org.springframework.security.access.vote.RoleVoter">
                <!-- 支持所有角色名称,无需前缀 -->
                <property name="rolePrefix" value=""/>
            </bean>
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
        </list>
    </constructor-arg>
</bean>

security提供了三种投票规则:

  • AffirmativeBased 只要有一个voter同意就通过
  • ConsensusBased 只要投同意票的大于投反对票的就通过
  • UnanimousBased 需要一致同意才通过

以下为AffirmativeBased决策过程

public void decide(Authentication authentication, Object object,
		Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
	int deny = 0;

	for (AccessDecisionVoter voter : getDecisionVoters()) {
		int result = voter.vote(authentication, object, configAttributes);

		switch (result) {
		case AccessDecisionVoter.ACCESS_GRANTED:
			return;

		case AccessDecisionVoter.ACCESS_DENIED:
			deny++;

			break;

		default:
			break;
		}
	}

	if (deny > 0) {
		throw new AccessDeniedException(messages.getMessage(
				"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
	}

	// To get this far, every AccessDecisionVoter abstained
	checkAllowIfAllAbstainDecisions();
}

SecurityMetadataSource

SecurityMetadataSource定义权限元数据(如资源与角色的关系),并提供了一个核心方法Collection<ConfigAttribute> getAttributes(Object object)来获取资源对应的角色列表,这种结构非常类似于Map。

security提供了DefaultFilterInvocationSecurityMetadataSource来进行角色读取操作,并将数据存储委托给一个LinkedHashMap对象。

<!-- 资源与角色关系元数据 -->
<bean id="securityMetadataSource"
            class="org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource">
    <constructor-arg index="0">
        <bean class="top.rainynight.site.core.RequestMapFactoryBean">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    </constructor-arg>
</bean>

DefaultFilterInvocationSecurityMetadataSource获取角色方法

public Collection<ConfigAttribute> getAttributes(Object object) {
	final HttpServletRequest request = ((FilterInvocation) object).getRequest();
	for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
			.entrySet()) {
		if (entry.getKey().matches(request)) {
			return entry.getValue();
		}
	}
	return null;
}

spring

security

Java

RMI

Servlet乱码分析

Bean Validation

Java中用js解析json

SQL 拼接

WebService

Java8 新特性

Java Concurrent

Java虚拟机

Effective Java

超类中的泛型

Custom Fileupload

可重入锁

Tomcat Https 配置

java spring security Java Mar 12, 2015