Spring Security是一个单独的项目,它可以无缝的和Spring MVC集成。Spring Security提供会特性保护web应用来自恶意的攻击。具体可以参看Spring Security的参考文档的章节,例如“CSRF protection”,“Security Response Headers”和“Spring MVC Integration”.注意:使用Spring Security去保护应用不是针对所有的特性。例如CSRF保护可以通过简单的添加CsrfFilter以及CsrfRequestDataValueProcessor来保护你的应用。可以查看Spring MVC Showcase做为一个例子。
另外一个选择是使用专门针对Web安全的框架,HDIV就是这样的一个框架并且可以和Spring MVC集成。
其实上面就是Spring官网对于Spring关于Web Security的介绍。如果就这样我觉得东西太少了。但是如果叫我详细的来解释一下Spring Security那么东西又太多了。那么我就针对一下Spring Security的里面的概念,以及它的入口还有它的源码来给大家讲一下Spring Security是如何做到Web Security的。
1、Spring Security Overview
下面是Spring Security官方的一个关于它的架构图。
下面来介绍一下主要的概念:
AbstractSecurityInterceptor
:是一个抽象类,实现安全对象的安全拦截,抽象了基于HTTP资源与方法资源的安全验证。
AuthenticationManager
:处理验证请求,相当于用户登录验证。
AccessDecisionManager
:访问投票管理器。相当于用户权限验证。
SecurityMetadataSource
:需要来实现,用于储存并且能够确定ConfigAttribute(用户配置的属性)可以适用于一个给定的安全对象调用。
RunAsManager
:创建一个临时的Authentication(验证)对象仅用来验证当前安全对象调用。
AfterInvocationManager
:评论安全对象调用返回的对象,能够修改返回的对象或者throw an AccessDeniedException.
MethodSecurityInterceptor
:提供基于方法级别的验证。
AspectJMethodSecurityInterceptor
:AspectJ JoinPoint安全拦截,包装JoinPoint到MethodInvocation代理使得它能够兼容security只能够支持MethodInvocation基础架构类。
FilterSecurityInterceptor
:通过Filter的形式执行HTTP资源的安全处理。
2、Spring Security Entrance
这次我们主要是讲Spring Security关于HTTP资源的安全处理,根据官方文档,使用Spring Security第一件事就是需要在web.xml文件中添加下面的filter描述:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这个配置会提供一个钩子到Spring Security web基础设施中。DelegatingFilterProxy是Spring Framework类用于代理到一个Filter实现,它是被声明在你的application context的Spring bean中。在这种情况下,这个bean会被命名为”springSecurityFilterChain”,它是一个被命名空间创建内部的基础设施bean用于处理web安全。注意:你不能够使用你自定义的bean名称。一旦你添加这个到你的web.xml文件中,你就可以开始修改你的application context文件了。Web安全服务配置使用元素。
3、Spring Security Process
首先展示一下Spring Security处理时序图:
其实里面最重要还是beforeInvocation(),finallyInvocation(),afterInvocation().下面我们来具体分析一下这三个方法的代码。大家注意结合Spring Security Overview中的概念对比来看,就会清晰的认识到这个组件的真实意义了。
1)beforeInvocation
主要包括用户的验证与授权。
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 1、获取用户资源(SecurityMetadataSource)
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 2、用户登录验证(AuthenticationManager)
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 3、用户授权验证(AccessDecisionManager)
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// 4、试图运行一个不同的用户(RunAsManager)
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
2)finallyInvocation
设置用户的SecurityContext信息。
protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
}
// 设置SecurityContext信息
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
3)afterInvocation
授权的后置处理,评论安全对象调用返回的对象,能够修改返回的对象或者throw an AccessDeniedException.
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
// 后置处理(AfterInvocationManager)
returnedObject = afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
token.getSecureObject(),
token.getAttributes(), returnedObject);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token
.getAttributes(), token.getSecurityContext().getAuthentication(), accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
}
return returnedObject;
}
通过这三个主要的方法是不是把Spring Security当中的组件都组合起来了。就能够达到用户验证与用户授权了。