spring security部分源码分析 鉴权流程

鉴权的流程在FilterSecurityInterceptor中
我们需要先执行登陆的操作,然后访问一个需要有权限鉴定的接口,进入org.springframework.security.web.access.intercept.FilterSecurityInterceptor#doFilter方法

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		//把request, response, 过滤器链chain放到一个对象FilterInvocation中
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}
	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if ((fi.getRequest() != null)
				//false
				&& (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 {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null);
		}
	}

进入org.springframework.security.access.intercept.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());
		}
		//获得配置类中配置的权限路径(http.authorizeRequests().antMatchers...)
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				//然后通过请求request的路径遍历查询是否需要权限,并返回路径和需要哪些权限
				.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 {
			//使用决策管理器进行决策,(默认使用的AffirmativeBased:一票通过)
			//权限不足抛出AccessDeniedException异常
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			//发布事件
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));
			//继续向上抛,在org.springframework.security.web.access.ExceptionTranslationFilter#doFilter中被catch住
			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);
		}
	}

org.springframework.security.access.vote.AffirmativeBased#decide

public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int deny = 0;
		//遍历,其实只有一个投票者WebExpressionVoter
		for (AccessDecisionVoter voter : getDecisionVoters()) {
			//开始投票,判断用户有没有权限
			//authentication中有用户权限,object:当前访问路径,configAttributes:路径需要的权限
			//-1不通过 0弃权 1通过 
			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:
				//不通过的次数+1
				deny++;

				break;

			default:
				break;
			}
		}
		//抛出权限不足的异常,上面的方法catch住了
		if (deny > 0) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}

		// To get this far, every AccessDecisionVoter abstained
		checkAllowIfAllAbstainDecisions();
	}

security是通过一系列过滤器链来执行逻辑的,请求进来会经过ExceptionTranslationFilter,然后由ExceptionTranslationFilter调用FilterSecurityInterceptor#doFilter方法鉴权,认证失败的异常会在这里被处理

org.springframework.security.web.access.ExceptionTranslationFilter#doFilter
认证失败的时候进入这里

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			//这里调用的前面FilterSecurityInterceptor的鉴权逻辑
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex) {
			throw ex;
		}
		//catch抛出的异常
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			//是否是认证失败的异常
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			if (ase == null) {
				//是否是没有权限的异常
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}
				//处理抛出的异常
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw new RuntimeException(ex);
			}
		}
	}

进入org.springframework.security.web.access.ExceptionTranslationFilter#handleSpringSecurityException

	private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		//是否是认证异常
		if (exception instanceof AuthenticationException) {
			logger.debug(
					"Authentication exception occurred; redirecting to authentication entry point",
					exception);

			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
		//是否是权限异常
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			//是否是匿名用户
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				//是匿名用户
				logger.debug(
						"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
						exception);

				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				//不是匿名用户
				logger.debug(
						"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
						exception);
				//进入自定义的权限不足处理器 默认使用AccessDeniedHandlerImpl如下图
				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Security 是一个广泛使用的开源框架,用于实现企业级应用程序的安全性,包括身份验证、授权和会话管理等。新版本(如Spring Security 5.x或以上)提供了高度灵活的自定义鉴权机制,以便开发者根据项目需求创建自己的认证策略。 1. 自定义认证器(Custom Authentication Provider): 在Spring Security中,你可以创建一个实现了`AuthenticationProvider`接口的类,这个接口定义了认证过程的主要逻辑,比如从数据库、API或其他源获取用户信息并验证其凭证。 ```java public class CustomAuthProvider implements AuthenticationProvider { // 实现 authenticate 和 supports 方法 public Authentication authenticate(Authentication authentication) throws AuthenticationException {...} public boolean supports(Class<?> authentication) {...} } ``` 2. 自定义用户DetailsService: 如果你想自定义如何加载和检索用户信息,可以实现`UserDetailsService`接口。这个接口主要用于在认证过程中查找用户的详细信息。 ```java @Service public class CustomUserService extends UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {...} } ``` 3. 自定义访问决策管理器(AccessDecisionManager): 这个组件负责决定是否允许用户访问某个资源。你可以创建一个实现了`AccessDecisionManager`接口的类,或者使用`AbstractAccessDecisionManager`作为基础,自定义授权规则。 4. 使用`WebSecurityConfigurerAdapter`或`SecurityWebApplicationInitializer`: 在Spring Boot应用中,你可以继承这些基类来自定义全局的安全配置。通过`@Configuration`注解,可以在类中添加自定义的过滤器、授权规则等。 5. 注解支持:Spring Security提供了许多注解,如`@PreAuthorize`、`@PostAuthorize`等,可以直接在方法上标记安全规则。你也可以自定义这些注解处理器,以便处理更复杂的授权逻辑。 相关问题-- 1. 如何在Spring Security中注册自定义的AuthenticationProvider? 2. 如何在`WebSecurityConfigurerAdapter`中集成自定义的用户认证逻辑? 3. 什么是`@PreAuthorize`注解,它是如何影响自定义鉴权的?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值