SpringSecurity解决公共接口自定义权限验证失效问题,和源码分析

背景:自定义权限认证,一部分接口必须要有相应的角色权限,一部分接口面向所有访问者,一部分接口任何人都不能访问。但是在使用 SpringSecurity的过程中发现,框架会将没有指定角色列表的URL资源直接放行,不做拦截。

用户登录认证成功后,携带Token访问URL资源,spring security 根据Token(请求头Authorization中)来分辨不同用户。

用户权限数据源是一个Map:以 URL资源为Key,以有权访问的Key的角色列表为Value。

使用时发现当一个接口有Key,但是Value为空或null时,spring security 框架自动放行,导致了权限失效问题。

解决方法有两种:

第一种方法:
默认rejectPublicInvocations为false。
对需要控制权限的URL资源添加标志,以防止roleList为空,跳过了权限验证.
公共权限设置为null,不进行权限验证

第二种方法:
配置rejectPublicInvocations为true
此后roleList为空,或者没有找到URL资源时,都为拒绝访问
需要控制权限的URL资源,即使对应角色为空,也会进行权限验证
公共权限设置为所有角色和匿名角色,不进行权限验证

package org.springframework.security.access.intercept;
/**
 * 对安全对象(访问请求+用户主体)拦截的抽象类源码
 */
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {

	// ... 其他方法省略
	
	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);
				
		// 框架在此处判断URL资源对应的角色列表是否为空
		if (attributes == null || attributes.isEmpty()) {
			// rejectPublicInvocations默认为false 
			// 可以配置为true,即角色列表为空的时候不进行放行
			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);
		}
		
		// 如果当前用户权限对象为null
		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,此处调用accessDecisionManager 进行鉴权
		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,这里可以另外配置或修改用户的权限对象,特殊场景使用
		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);
		}
	}
	// ... 其他方法略
}

你可能感兴趣的:(Java,Spring,Security)