概述
此过滤器FilterSecurityInterceptor
是一个请求处理过程中安全机制过滤器链中最后一个filter
,它执行真正的HTTP
资源安全控制。
具体代码实现上,FilterSecurityInterceptor
主要是将请求上下文包装成一个FilterInvocation
然后对它进行操作。FilterSecurityInterceptor
仅仅包含调用FilterInvocation
的主要流程。具体的安全控制细节,在其基类AbstractSecurityInterceptor
中实现。
源代码解析
package org.springframework.security.web.access.intercept;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
// ~ Static fields/initializers
// =================================================================
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
// ~ Instance fields
// =================================================================
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
// ~ Methods
// =================================================================
public void init(FilterConfig arg0) throws ServletException {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 封装请求上下文为一个FilterInvocation,然后调用该FilterInvocation执行安全认证
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
// 如果被指定为在整个请求处理过程中只能执行最多一次 ,并且监测到已经执行过,
// 则直接放行,继续 filter chain 的执行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
// 如果被指定为在整个请求处理过程中只能执行最多一次 ,并且监测到尚未执行,
// 则设置已经执行标志,随后执行职责逻辑
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 这里是该过滤器进行安全检查的职责逻辑,具体实现在基类AbstractSecurityInterceptor
// 主要是进行必要的认证和授权检查,如果遇到相关异常则抛出异常,之后的过滤器链
// 调用不会继续进行
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 如果上面通过安全检查,这里继续过滤器的执行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
// 指定是否在整个请求处理过程中该过滤器只被执行一次,缺省是 true。
// 也存在在整个请求处理过程中该过滤器需要执行多次的情况,比如JSP foward/include
// 等情况。
public boolean isObserveOncePerRequest() {
return observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
// AbstractSecurityInterceptor 类源代码
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());
}
// 从安全配置中获取安全元数据,记录在 attributes
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));
// 该资源没有设置安全,可以公开访问,不做相应的安全检查,返回 null,
// 表示不需要做后续处理
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 如果安全认证token不存在,则抛出异常 AuthenticationCredentialsNotFoundException
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 如果安全认证token存在,则检查是否需要认证,如果需要,则执行认证并更行
// 安全上下文中的安全认证token,如果认证失败,抛出异常 AuthenticationException
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 现在已经确保用户通过了认证,现在基于登录的当前用户信息,和目标资源的安全配置属性
// 进行相应的权限检查,如果检查失败,则抛出相应的异常 AccessDeniedException
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));
}
// Attempt to run as a different user
// 如果设置了 RunAsManager, 尝试将当前安全认证token修改为另外一个run-as用户,
// 缺省是 NullRunAsManager, 其实相当于没有启用 run-as, 下面的 runAs 缺省会是
// null
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
// 注意这里第二个参数为 false, 表示请求处理完之后再次回到该filter时不需要在刷新安全认证token
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
// 注意这里第二个参数为 true, 表示请求处理完之后再次回到该filter时需要在刷新安全认证token :
// 恢复到 run-as 之前的安全认证token
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}