spring security FilterSecurityInterceptor过滤器访问控制流程分析

FilterSecurityInterceptor.doFilter中调用了当前类的invoke(fi)方法参数fiFilterInvocation(调用的对象内部封装了当前请求的一些列信息),该方法主要流程如下

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访问控制决策管理器

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值