Spring Security Web 5.1.2 源码解析 -- FilterSecurityInterceptor

概述

此过滤器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);
		}
	}

其他文章

Spring Security Web 5.1.2 源码解析 – 安全相关Filter清单

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值