流程圖
類和接口介紹
- FilterSecurityInterceptor:是整個權限判斷流程的入口,包含着請求的相關信息;
- AccessDecisionManager:是一個接口,有一個抽象實現(AbstractAccessDecisionManager)和三個具體實現(AffirmativeBased、ConsensusBased和UnanimousBased)
- AbstractAccessDecisionManager: 該實現中維護者一組AccessDecisionVoter接口;
- AccessDecisionVoter:對每個權限請求進行投票(Spring Security3之前的voter);
- AffirmativeBased:積極的策略,即對於一個權限判斷,不管有多少個Voter投不通過,只要有一個投票通過,該權限就通過;該策略是spring默認的判斷策略;
- ConsensusBased:共識的策略,即比較投票通過和不通過的票數,以票數最多的爲準;
- UnanimousBased:全部通過策略,即對於一個權限判斷,不管有多少個投票通過,只要有一個投票不通過,該權限就不通過;
- SecurityConfig:Spring Security的配置文件,用於配置哪些請求放行,哪些請求要經過過濾器以及tokenStoke、enhance等;
- ConfigAttribute:FilterSecurityInterceptor會從SecurityConfig中讀取配置信息,並封裝成一組ConfigAttribute對象,每個ConfigAttribute對象代表着一個URL它所需要的權限;
- Authentication:封裝着當前用戶所擁有的權限信息;
- WebExpressionVoter: Spring Security3新增的voter,在web服務中它包含了所有的voter,即它投票過就通過、不過就不通過;
該流程中一共包含三組信息:1. FilterSecurityInterceptor所攜帶的用戶請求信息,2. SecurityConfig配置的權限信息,3. Authentication當前用戶所擁有的權限信息。 將這三組權限信息傳遞給AbstractAccessDecisionManager,它通過AccessDecisionVoter對當前請求是否擁有相應權限進行投票,spring根據設定的投票策略判斷當前用戶是否擁有他所請求資源的權限。
源碼分析
FilterSecurityInterceptor:doFilter方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 參考下方:invoke方法
invoke(fi);
}
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
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 在調用後面的過濾器之前,調用AbstractSecurityInterceptor的beforeInvocation方法
// 參考下方:beforeInvocation方法
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
AbstractSecurityInterceptor: 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());
}
// 獲取當前請求應該具有的權限信息
// 參考下方getAttributes方法
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);
}
// 獲取當前用戶所擁有的權限信息
// 參考下方:authenticateIfRequired方法
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 調用AffirmativeBased的decide方法
// authenticated: 封裝了當前用戶所擁有的權限信息
// object: 封裝了當前請求所攜帶的信息
// attributes: 封裝了當前請求應該具有的權限信息
// 參考下方:decide方法
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
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);
}
}
DefaultFilterInvocationSecurityMetadataSource: getAttributes方法
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
// 1. SecurityConfig 配置的URL權限信息都會在requestMap中記錄
// 2. 判斷當前請求中是否有與配置信息匹配,如果匹配的話獲取到當前請求應有的權限
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
AbstractSecurityInterceptor: authenticateIfRequired方法
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
AffirmativeBased:decide方法
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
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();
}
}