SpringSecurity架构
从万米高空看Spring Security
如果站在一个很高的层次看Spring Security的架构的话,Spring的拦截器模型主要用于两个领域:Web 请求 和 方法调用。Spring Security将web资源和应用程序方法通过过滤器和拦截器封装在其内部,只有满足特定的条件才能访问。Web 请求 和 方法调用的访问控制都是基于核心拦截器组件(SecurityInteceptor),拦截器组件包含了核心的访问控制逻辑。

站在Spring Security的塔尖
再近一点,你可以看到Spring Security的各个协作组件。期中包含:
1、用于配置的XML Namespace和加工之后的配置信息ConfigAtrribute
2、过滤器链中包含的各个过滤器
3、核心拦截器SecurityInteceptor的各个组件及其协作组件:AuthenticationManager、AuthenticationProvider、UserDetailService、AccessDecisionManager等
4、用于存放SpringSecurity上下文的容器:SecurityContextHolder
5、用于存放用户认证信息的核心类:Authentication
6、用于访问控制的核心组件ACL

XML Namespace
Spring 从2.0版本开始支持自定义命名空间。自定义命名空间其实可以看做是一种语法糖,本质上是一种基于XML的DSL(Domain Specific Language)。在Spring框架中自定义XML命名空间也十分简单:
1、 定义一个`xsd`文件来描述`XML`文件的格式
2、在 `spring.schemas` 文件中描述`xsd`文件的`URL`地址与本地存储位置之间的对应关系
3、在 spring.handlers 文件中指定自定义命名空间的处理器`handler`(自己实现)
4、一组负责解析自定义命名空间中各个元素的`parser`(自己实现)
spring.schemas和spring.handlers均在META-INF文件夹下,以Spring Security为例,说明:
spring.schemas文件中指定了本地xsd文件与URL之间对应关系:

spring.handlers文件中指定了命名空间的处理类:

SecurityNamespaceHandler实现了命名空间的解析:
public final class SecurityNamespaceHandler implements NamespaceHandler {
public BeanDefinition parse(Element element, ParserContext pc) {
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = parsers.get(name);
if (parser == null) {
// SEC-1455. Load parsers when required, not just on init().
loadParsers();
}
if (parser == null) {
if (Elements.HTTP.equals(name)
|| Elements.FILTER_SECURITY_METADATA_SOURCE.equals(name)
|| Elements.FILTER_CHAIN_MAP.equals(name)
|| Elements.FILTER_CHAIN.equals(name)) {
reportMissingWebClasses(name, pc, element);
}
else {
reportUnsupportedNodeType(name, pc, element);
}
return null;
}
return parser.parse(element, pc);
}
public void init() {
loadParsers();
}
private void loadParsers() {
// Parsers
parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_PROVIDER,
new AuthenticationProviderBeanDefinitionParser());
parsers.put(Elements.GLOBAL_METHOD_SECURITY,
new GlobalMethodSecurityBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_MANAGER,
new AuthenticationManagerBeanDefinitionParser());
parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
new MethodSecurityMetadataSourceBeanDefinitionParser());
// Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass()
.getClassLoader())) {
parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE,
new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
}
if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER,
new WebSocketMessageBrokerSecurityBeanDefinitionParser());
}
}
}
同时SecurityNamespaceHandler加载了一组Parser来具体负责各个元素的解析。
Spring Security启动时XML命名空间的加载过程:

Servlet Filters
Spring Security基于过滤器链模型保护Web应用的安全,该模型基于标准的Servlet Filter。过滤器链由一组单一职责的过滤器组成,每个过滤器负责一个功能,他们共同完成Web安全保护。过滤器链中的过滤器是一个个的Spring Bean,然而标准的Servlet应用是无法感知Spring Bean的。因此需要一个特殊的Servlet Filter跨越标准Servlet API 与Spring应用的边界,将二者联系起来。
DelegatingFilterProxy
DelegatingFilterProxy在Spring初始化后,从Spring容器中获取代理Bean,并将Filter的执行逻辑委托给该代理Bean,从而将标准Servlet与Spring应用连接起来.在Spring Security应用中,DelegatingFilterProxy在启动时查找名称为springSecurityFilterChain的Bean,该Bean正是由Spring启动时构建的过滤器链。

DelegatingFilterProxy是一个标准的Servlet Filter,他在Servlet容器启动时被加载。同时又是一个GenericFilterBean(该Bean的作用是将Filter中的initParameter赋值给Spring Bean属性),在Spring容器加载后将初始化参数赋值给Spring容器中的Bean,Bean的名称是:DelegatingFilterProxy作为Filter创建时指定的初始化参数targetName,默认为springSecurityFilterChain.
SpringSecurityFilterChain
Spring容器中beanName为springSecurityFilterChain的bean,是一个FilterChainProxy实例,该实例中包含Spring Security的各种过滤器,以满足应用使用。这些过滤器在Spring容器启动时根据配置完成初始化,每一个过滤器提供一种功能,很好的遵循了单一职责原则。

实际上,Spring Security提供的过滤器远不止这些,全部的过滤器以enum的形式被定义在org.springframework.security.config.http.SecurityFilters中:
enum SecurityFilters {
FIRST(Integer.MIN_VALUE),
CHANNEL_FILTER,
SECURITY_CONTEXT_FILTER,
CONCURRENT_SESSION_FILTER,
WEB_ASYNC_MANAGER_FILTER /** {@link WebAsyncManagerIntegrationFilter} */,
HEADERS_FILTER, CORS_FILTER,
CSRF_FILTER,
LOGOUT_FILTER,
X509_FILTER,
PRE_AUTH_FILTER,
CAS_FILTER,
FORM_LOGIN_FILTER,
OPENID_FILTER,
LOGIN_PAGE_FILTER,
LOGOUT_PAGE_FILTER,
DIGEST_AUTH_FILTER,
BEARER_TOKEN_AUTH_FILTER,
BASIC_AUTH_FILTER,
REQUEST_CACHE_FILTER,
SERVLET_API_SUPPORT_FILTER,
JAAS_API_SUPPORT_FILTER,
REMEMBER_ME_FILTER,
ANONYMOUS_FILTER,
SESSION_MANAGEMENT_FILTER,
EXCEPTION_TRANSLATION_FILTER,
FILTER_SECURITY_INTERCEPTOR,
SWITCH_USER_FILTER,
LAST(Integer.MAX_VALUE);
}
这些过滤器的功能简介如下:
| 过滤器名称
| 类名 | 作用 |
|---|---|
CHANNEL_FILTER ChannelProcessingFilter |
确保请求是由正确的通道处理。通常情况下用于决定一个web请求是否需要经过https处理 |
SECURITY_CONTEXT_FILTERSecurityContextPersistenceFilter |
填充SecurityContextHolder,创建SecurityContext供框架使用 |
CONCURRENT_SESSION_FILTERConcurrentSessionFilter |
并发Session处理机制的一部分。其主要作用是在每个用户的并发Session数量达到最大时,校验Session是否过期,如果过期就退出。 |
WEB_ASYNC_MANAGER_FILTERWebAsyncManagerIntegrationFilter |
提供了对SecurityContext和WebAsyncManager的集成,将SecurityContext设置到Callable上 |
LOGOUT_FILTERLogoutFilter |
拦截特定的URL(默认为/logout),在用户退出时完成特定工作:清除cookie,删除remember me和 SecurityContext信息等 |
X509_FILTERX509AuthenticationFilter |
使用 X509Certificate从请求中提取Principal和Credential,并尝试使用这些信息进行认证 |
PRE_AUTH_FILTERJ2eePreAuthenticatedProcessingFilter |
使用J2ee认证机制作为预认证。 |
CAS_FILTERCasAuthenticationFilter |
单点登录认证支持 |
FORM_LOGIN_FILTERUsernamePasswordAuthenticationFilter |
提供基于用户名密码的认证机制 |
OPENID_FILTEROpenIDAuthenticationFilter |
提供基于openId的身份证机制 |
LOGIN_PAGE_FILTERLoginPageGeneratingWebFilter |
生成默认的登录页 |
LOGOUT_PAGE_FILTERLogoutPageGeneratingWebFilter |
生成默认的退出页 |
DIGEST_AUTH_FILTERDigestAuthenticationFilter |
支持HTTP摘要认证机制。查找Digest和HTTP认证头,并进行认证,认证成功后生成Authentication对象 |
BEARER_TOKEN_AUTH_FILTEROAuth2AuthenticationProcessingFilter |
支持OAuth2认证方式 |
BASIC_AUTH_FILTERBasicAuthenticationFilter |
支持 HTTP BASIC认证方式 |
REQUEST_CACHE_FILTERRequestCacheAwareFilter |
取出被缓存的请求,重新请求。用于用户登录成功后,重新恢复因为登录被打断的请求 |
SERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilter |
将请求包装在实现Servlet API安全方法(如isUserInRole)的请求包装器中,并将其委托给SecurityContextHolder,使得用户能使使用Servlet API方便的获取认证信息。 |
JAAS_API_SUPPORT_FILTERJaasApiIntegrationFilter |
从请求中获取Subject,然后以该Subject执行过滤器链 |
REMEMBER_ME_FILTERRememberMeAuthenticationFilter |
如果开启了“记住我”功能,该过滤器将尝试自动登录,并使用“记住我”里面的信息进行身份认证 |
ANONYMOUS_FILTERAnonymousAuthenticationFilter |
检查SecurityContext中是否已有Authentication,如果没有,创建一个匿名的Authentication。 |
SESSION_MANAGEMENT_FILTERSessionManagementFilter |
将Authentication对象传递给Session管理器,做一些校验和Session相关的处理,目前只有一个ConcurrentSessionControlStrategy,处理Session并发问题 |
EXCEPTION_TRANSLATION_FILTERExceptionTranslationFilter |
处理Spring Security异常与HTTP STATUS CODE之间的与转换工作。 |
FILTER_SECURITY_INTERCEPTORFilterSecurityInterceptor |
处理具体的授权工作。根据Authenticaiton对象与配置的权限信息决定能否访问该URL |
SWITCH_USER_FILTERSwitchUserFilter |
为Spring应用提供类似Linux中 su命令的功能,当期用户可以切换至拥有更高权限的用户。 |
ConfigAttribute
ConfigAttribute包含了Spring Security进行资源保护的配置元信息。其类图如下:

假如你配置了如下信息:
<security:intercept-url pattern="/hello" access="ROLE_SIMPSON_MEMBER" />
Spring Security将使用 FilterInvocationSecurityMetadataSourceParser解析这段配置,pattern部分作为一个AntPattern被解析成一个RequestMatcher实例,而access部分被解析成一个ConfigAttribute,将RequestMatcher作为key,ConfigAttribute作为value组成一个map集合,放置到FilterInvocationSecurityMetadataSource中,在FilterSecurityInterceptor中使用。解析过程如下:

如果需要方法级别的安全,则需要开启@EnableGlobalMethodSecurity或在xml配置中添加如下代码:
<global-method-security secured-annotations = "enabled"/>
Spring将在启动时创建 MethodSecurityMetadataSourceAdvisor实例,并注册到容器中。该 Advisor作为一个基础的 Adivsor被 InfrastructureAdvisorAutoProxyCreator(实际上是一个 BeanPostProcessor)识别,该InfrastructureAdvisorAutoProxyCreator扫描容器中所有的bean,判断这些bean是否可以应用MethodSecurityMetadataSourceAdvisor(取决于Bean的方法上是否有@Secured注解),如果可以应用,则使用该 Advisor封装bean.
具体过程是:对于每一个Bean及其方法都调用 MethodSecurityMetadataSource.getAttributes方法,判断是否有ConfigAttribute,如果有,InfrastructureAdvisorAutoProxyCreator则调用其 createProxy方法应用 MethodSecurityMetadataSourceAdvisor(内部包含securityMethodInterceptor作为Advice)创建代理。

Authentication
一个 Authentication对象代表一个登陆进系统的实体,比如:一个登陆的用户就是一个 Authentication对象。Authentication对象有以下几个实现类:

public interface Authentication extends Principal, Serializable {
//权限
Collection<? extends GrantedAuthority> getAuthorities();
//凭据,密码
Object getCredentials();
//详细信息
Object getDetails();
//主体,账号信息
Object getPrincipal();
//是否已经过认证
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
| 实现类 | 说明 |
|---|---|
UsernamePasswordAuthenticationToken |
用于用户名密码认证模式 |
OpenIDAuthenticationToken |
用于OpenID 认证模式 |
PreAuthenticatedAuthenticationToken |
用于预认证模式,所谓预认证模式是指实际认证过程是由外部系统负责,Spring Security只从外部系统的消息中提取认证的principal信息 |
SecurityContext
SecurityContext是Spring用于存储Authentication对象的地方,它将Authentication对象与当前线程绑定,放置到ThreadLocal中。SecurityContextHolder使得可以在应用的任何地方访问SecurityContext,并使用策略模式满足不同的应用场景。

AuthenticationProvider
AuthenticationProvider主要用于认证一个Authentication对象。其主要定义了如下两个接口:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
并且有几种不同的实现类,分别适用于不同的场景:

AccessDecisionManager
AccessDecisionManager用于决定一个具体的Authentication是否具有访问特定资源的权限。在其实现中,它将逻辑委托给AccessDecisionVoter,AccessDecisionVoter只是简单的比较Authentication 对象中的GrantedAuthorities是否满足资源配置的ConfigAttribute需求。这些AccessDecisionVoters投票决定是否允许Authentication访问资源。AccessDecisionManager的实现使用特定的策略来考虑投票的结果,决定是否能够访问资源。

| 实现类 | 说明 |
|---|---|
AffirmativeBased |
在所有的Voter中,如果有一个或多于一个的Voter投赞成票,就可以访问资源。如果没有人投赞成票,且至少有一人投反对票,则抛出AccessDeniedException |
ConsensusBased |
只有赞成票的数量多于反对票的数量时,才能访问资源 |
UnanimousBased |
只有所有人投赞成票时,才能访问资源 |
AccessDecisionVoter
上面说AccessDecisionManager使用``AccessDecisionVoter进行投票,具体的投票过程依赖于特定的支持。其实现类如下:
| 实现类 | 说明 |
|---|---|
Jsr250Voter |
为带有JSR 250注解的资源进行投票,如@DenyAll,@PermitAll,@RolesAllowed等 |
PreInvocationAuthorizationAdviceVoter |
为带有@PreFilter和@PreAuthorize注解的资源进行投票,这两个注解的值支持SpEL表达式,PreInvocationAuthorizationAdviceVoter负责计算SpEL表达式的值,并根据结果进行投票 |
AclEntryVoter |
主要处理ACL规则,对领域模型对象进行投票,主要逻辑实现在AbstractAclVoter中。 |
AuthenticatedVoter |
如果资源的ConfigAttribute中引用了认证信息的三个级别(IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED_ANONYMOUSLY)之一,该 Voter就会进行投票 |
RoleVoter |
根据角色进行投票,应用最普遍的一个Voter |
WebExpressionVoter |
支持SpEL表达式的Voter,例如:hasRole('admin') |
UserDetailsService
UserDetailsService负责在身份验证请求到达应用程序时从底层用户存储(内存、数据库等)加载用户信息.该接口只定义了一个方法:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
其集成体系如下:

当请求需要认证时,主要有两种策略(AuthenticationUserDetailsService 和UserDetailsService)获取用户信息:UserDetailsService根据用户名获取UserDetails;AuthenticationUserDetailsService 根据 Authentication对象获取用户信息。AuthenticationUserDetailsService 一般用于预认证方式。例如:OpenIDAuthenticationProvider 和 CasAuthenticationProvider将获取UserDetails的逻辑委托给AuthenticationUserDetailsService 处理。
UserDetails
UserDetails对象包含了Spring Security上下文中的完整用户信息,该对象可以在应用的任意位置从SercurityContext中获得。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
ACL
ACL模块负责以细粒度在单个领域模型上保护应用。在这种情况下,需要为每一个领域模型对象创建一个ID,并创建领域模型与用户之间的关系,这种关系决定一个用户能否访问相关的领域模型对象。ACL提供了非常细粒度的访问控制。当前的ACL模块支持从关系数据库中读取访问规则。
浙公网安备 33010602011771号