为什么80%的码农都做不了架构师?>>>
序
本文就来研究一下spring security的FilterSecurityInterceptor
问题
前面的文章讲了SecurityContextPersistenceFilter是如何将context从session读取并写入的。其中还讲到了AbstractAuthenticationProcessingFilter是如何将鉴权成功的authentication写入context的。那么spring security是如何处理没有authentication的请求呢。答案就在FilterSecurityInterceptor。
filter顺序
spring security内置的各种filter:
Alias | Filter Class | Namespace Element or Attribute |
---|---|---|
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
HEADERS_FILTER | HeaderWriterFilter | http/headers |
CSRF_FILTER | CsrfFilter | http/csrf |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter Subclasses | N/A |
CAS_FILTER | CasAuthenticationFilter | N/A |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | N/A |
FilterSecurityInterceptor
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java
/*** Performs security handling of HTTP resources via a filter implementation.* <p>* The <code>SecurityMetadataSource</code> required by this security interceptor is of* type {@link FilterInvocationSecurityMetadataSource}.* <p>* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.* </p>** @author Ben Alex* @author Rob Winch*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implementsFilter {
/*** Method that is actually called by the filter chain. Simply delegates to the* {@link #invoke(FilterInvocation)} method.** @param request the servlet request* @param response the servlet response* @param chain the filter chain** @throws IOException if the filter chain fails* @throws ServletException if the filter chain fails*/public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);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 checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// first time this request being called, so perform security checkingif (fi.getRequest() != null) {fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {super.finallyInvocation(token);}super.afterInvocation(token, null);}}//......
}
所有的请求到了这一个filter,如果这个filter之前没有执行过的话,那么首先执行的InterceptorStatusToken token = super.beforeInvocation(fi);这个是由AbstractSecurityInterceptor提供。它就是spring security处理鉴权的入口。
AbstractSecurityInterceptor
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java
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());}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 authorizationtry {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 userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn 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-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);}}
这里调用了accessDecisionManager来进行判断,如果没有登录态,在到达这个filter之前会先经过AnonymousAuthenticationFilter.java,其Authentication的值为AnonymousAuthenticationToken。如果匿名请求需要登录态的url,或者权限不够,则抛出AccessDeniedException。
小结
spring security两个入口filter分别如下:
- AbstractAuthenticationProcessingFilter(
主要处理登录
) - FilterSecurityInterceptor(
主要处理鉴权
)
而SecurityContextPersistenceFilter主要是为这两个filter准备context。