FilterSecurityInterceptor.doFilter
中调用了当前类的invoke(fi)
方法参数fi
为FilterInvocation
(调用的对象内部封装了当前请求的一些列信息),该方法主要流程如下
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//判断当前请求的request不为null,FILTER_APPLIED这个属性限制
//了当前类型的过滤器仅执行一个,它结合observeOncePerRequest
//这个属性进行灵活配置,假如需要执行多次可以设置observeOncePerRequest为false
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
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);
}
//调用前对请求进行处理
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
//return前处理
super.finallyInvocation(token);
}
//调用后处理
super.afterInvocation(token, null);
}
}
从父类的三个方法很容易可以看出来这是一个aop;
super.beforeInvocation(fi);
super.afterInvocation(token, null);
super.finallyInvocation(token);
对一个方法得访问控制肯定是再前置处理器内,即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());
}
//从传进来得 调用对象中即FilterInvocation 提取访问连接所需要得权限信息
//如果权限信息为空,并且拒绝发布调用事件则直接抛出非法参数异常,如果不拒绝发
//布事件则发布调用事件,此处因认为是调用失败,发布调用事件与是否调用成功无关
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);
}
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
//将认证信息,调用对象,以及调用该对象所需要得权限信息列表传入决策管理器进行
//决策,如果异常则抛出拒绝访问异常,并发布授权失败事件。
try {
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
//返回一个新得认证对象,用于修改securityContex中得认证对象,此处似乎
//没有什么作用,可能只是一个范例告诉你可以这么写,当然提供得这个功能。
//你也可以对它进行设置,默认是个null这个替换我不知道具体
//应用场景,官方得介绍也不是很详细,如有知道得给个评论谢谢。在后续得请求
//请求调用中,拿到得securitycontex就不是原来得内容了。
//官方介绍https://docs.spring.io/spring-security/site/docs/current/reference/html5/#runas
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
//最后将原securitycongtex封装成一个InterceptorStatusToken 返回
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
在前置处理完成后调用完业务逻辑 之后进行后置处理
我们来看源码:
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
//将原认证信息设置回securitycontex,替换上面提到得被替换掉得认证信息。
finallyInvocation(token); // continue to clean in this method for passivity
//如果配置了后置管理器,则执行后置管理器得决策流程,后置决策器中,通过
//provider作为具体逻辑处理。
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
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;
}
在返回前又调用了一次finallyInvocation,把原认证信息还原。因为在后置处理器中有可能没处理到。所以这里加了一层保障。
finallyInvocation 代码:
protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
那么接着来看决策管理器
参考另一篇文章 【spring security访问控制决策管理器】