Spring Security授权源码分析

news/2024/7/3 13:35:41

授权是在用户认证通过后,对访问资源的权限进行检查的过程。
Spring Security使用标准 Filter建立了对 web请求的拦截,最终实现对资源的授权访问。

  • Spring Security过滤器链加载执行流程源码分析请查看:https://blog.csdn.net/qq_42402854/article/details/122205790

在这里插入图片描述
关于授权,这里查看几个重要的 Filter 的处理逻辑。

一、SecurityContextPersistenceFilter

针对 ServletRequest进行了一次包装,使得 request具有更加丰富的 API。

查看 doFilter方法:
在这里插入图片描述

  • 通过repo.loadContext从请求中获取session,然后将 session信息保存在context中,方便后面 filter直接获取当前的用户信息。
  • 通过 SecurityContextHolder.setContext(contextBeforeChainExecution); 设置 context信息到contextHolder中(将context信息放在了ThreadLocal中,线程安全的)。

1)repo.loadContext方法
在这里插入图片描述

2)setContext方法
在这里插入图片描述

二、AnonymousAuthenticationFilter

Spring Security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

AnonymousAuthenticationFilter过滤器是在 UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,即认证信息为空,AnonymousAuthenticationFilter会创建一个匿名用户(一个 Authenticaiton 的匿名实现类 AnonymousAuthenticationToken)存入到当前 SecurityContextHolder中。

查看 doFilter方法:
在这里插入图片描述

  • 判断 SecurityContextHolder中Authentication为否为空;
  • 如果空则为当前的 SecurityContextHolder中添加一个匿名的 AnonymousAuthenticationToken(用户名为 anonymousUser的 AnonymousAuthenticationToken)

三、ExceptionTranslationFilter

ExceptionTranslationFilter异常处理过滤器,用来处理在系统认证授权过程中抛出的异常(也就是下一个 FilterSecurityInterceptor过滤器)。

ExceptionTranslationFilter过滤器主要是拦截处理 AuthenticationExceptionAccessDeniedException异常并添加到HTTP响应中。其他异常它会捕获 Filter处理的异常,抛给下一个 Filter去处理。

查看 doFilter方法:
在这里插入图片描述
在这里插入图片描述

四、FilterSecurityInterceptor

FilterSecurityInterceptor过滤器是认证授权过滤器链中最后一个过滤器,获取所配置资源访问的授权信息,根据 SecurityContextHolder中存储的用户信息来决定其是否有权限。

该过滤器之后就交由 Spring MVC,访问到我们的controller方法。

查看 doFilter方法:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        this.invoke(fi);
    }

	/**
	 *  doFilter实际执行的方法
	 * @param filterInvocation 封装了request response 过滤器链的对象
	 */
	public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		// 1. 如果已经执行过该过滤器,直接放行
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// first time this request being called, so perform security checking
		// 2. 第一次调用这个请求,所以执行安全检查
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
		// 4. 前置访问控制处理
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
			// 5. 放行
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		} finally {
			super.finallyInvocation(token);
		}
		// 6. 后置处理
		super.afterInvocation(token, null);
	}

比较重要的方法

  • beforeInvocation
  • finallyInvocation
  • afterInvocation

1、查看 beforeInvocation方法

进入父类 AbstractSecurityInterceptor的 beforeInvocation方法进行处理,最终返回一个 InterceptorStatusToken对象,它就是 Spring Security处理鉴权的入口。

    protected InterceptorStatusToken beforeInvocation(Object object) {
		// 1. 判断object是不是FilterInvocation
        if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException(...);
        } else {
            // 2. 获取配置的访问控制规则 any request => authenticated ,没有配置,return null
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

            ...

                // 3. 获取Authentication对象
                Authentication authenticated = this.authenticateIfRequired();

                try {
                    // 4. 进行授权判断(重点)
                    this.accessDecisionManager.decide(authenticated, object, attributes);
                } catch (AccessDeniedException var7) {
                    this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
                    throw var7;
                }

                if (debug) {
                    this.logger.debug("Authorization successful");
                }
				// 5. 发布授权成功
                if (this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }
				// 6. 对Authentication进行再处理,这里没有处理,直接返回null
                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                if (runAs == null) {
                    if (debug) {
						this.logger.debug("RunAsManager did not change Authentication object");
                    }
				// 7. 返回 InterceptorStatusToken
                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
                    if (debug) {
                        this.logger.debug("Switching to RunAs Authentication: " + runAs);
                    }

                    SecurityContext origCtx = SecurityContextHolder.getContext();
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                    SecurityContextHolder.getContext().setAuthentication(runAs);
                    // 7. 返回 InterceptorStatusToken
                    return new InterceptorStatusToken(origCtx, true, attributes, object);
                }
            } else if (this.rejectPublicInvocations) {
                throw new IllegalArgumentException(...);
            } else {
                if (debug) {
                    this.logger.debug("Public object - authentication not attempted");
                }

                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }

1.1 AffirmativeBased授权处理器

AccessDecisionManager 是如何授权的?
Spring Security默认使用 AffirmativeBased实现 AccessDecisionManager 的 decide 方法来实现授权。它会调用授权管理器进行决策,当失败发生异常时,会爆出异常。
在这里插入图片描述
调用 AccessDecisionVoter 进行vote(投票)

  • 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
  • 如果没有投通过则 ++deny ,
  • 最后判断if(deny>0 抛出AccessDeniedException(未授权)

在这里插入图片描述

1.2 WebExpressionVoter.vote()

这里简单查看 WebExpressionVoter.vote()方法:

    public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
        assert authentication != null;

        assert fi != null;

        assert attributes != null;

        // 1. 获取http配置项
        WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);
        // 2. 没有配置规则,弃权
        if (weca == null) {
            return 0;
        } else {
            // 3. 对EL表达式进行处理
            EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);
            ctx = weca.postProcess(ctx, fi);
            // 5. 符合条件,则投赞成票,否则反对票
            return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;
        }
    }

投票完成,回到 beforeInvocation方法,最后返回 InterceptorStatusToken对象。

2、查看 finallyInvocation方法

授权成功处理:没有抛出异常,则认为授权通过,FilterSecurityInterceptor会进入 finallyInvocation方法。
这个方法主要是判断需不需要重新设置 SecurityContext内容,这里没有配置,直接跳过。
在这里插入图片描述

3、查看 afterInvocation方法

接下来进入 afterInvocation方法,再次调用了 finallyInvocation方法,然后查询是否还有决策后置处理器,如果有,再次进行决策。
最后的最后,才代表授权成功,就交由 Spring MVC,访问到我们的 controller方法了。

    protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
        if (token == null) {
            return returnedObject;
        } else {
            this.finallyInvocation(token);
            if (this.afterInvocationManager != null) {
                try {
                    returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);
                } catch (AccessDeniedException var5) {
                    AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);
                    this.publishEvent(event);
                    throw var5;
                }
            }

            return returnedObject;
        }
    }

– 求知若饥,虚心若愚。


http://www.niftyadmin.cn/n/2257459.html

相关文章

mysql迁移

目录 1.SQLyog 2.阿里云DTS 1.创建任务 2.配置源库及目标库信息 3.配置任务对象及高级配置 4.高级配置 1.SQLyog 适用于离线迁移&#xff0c;全量迁移&#xff0c;少量迁移&#xff0c;数据库大小在几百兆范围内&#xff0c;时间需要几十秒到几分钟不等。 选择源库&…

榆熙电商:PDD商家如何留住核心消费者?

一般来说&#xff0c;很多个人商家在拼多多开店之后对自己店铺的运营管理上都比较欠缺&#xff0c;对寻找目标用户以及用户定位管理等方面都很被动&#xff0c;等待流量自主上门往往会丧失店铺竞争力。作为拼多多商家&#xff0c;如何才能对目标用户有着清晰明确的认知呢&#…

Spring Security实现记住我功能源码分析

Spring Security 实现“记住我”功能&#xff0c;即自动登录功能有两种方式&#xff1a; 将 token写入到浏览器的 Cookie中将 token持久化到数据库 一、将 token写入到浏览器的 Cookie中 1、代码实现 1.1 后端 Spring Security默认是没有开启“记住我”功能&#xff0c;我们…

榆熙电商:为何购物旗舰店选择优于其他?

用户们在购买某个店铺的产品的时候&#xff0c;往往都是优先选择在旗舰店进行购买&#xff0c;那么该如何去分辨拼多多的旗舰店呢&#xff1f;旗舰店有哪些保障呢&#xff1f;一起和成都榆熙小编来了解一下吧。 一、用户如何识别官方旗舰店&#xff1f; 最简单的方法就是看店…

Spring Security CSRF防御源码分析

一、CSRF简介 1、CSRF是什么&#xff1f; CSRF&#xff08;Cross-site request forgery&#xff09;&#xff0c;也被称为&#xff1a;one click attack/session riding&#xff0c;中文名称&#xff1a;跨站请求伪造&#xff0c;通常缩写为&#xff1a;CSRF/XSRF。 跨站请求…

榆熙电商:拼多多商家如何优化用户春节期间的购物体验?

春节将至&#xff0c;在拼多多开店的商家很可能会面临合作物流方春节期间不发货的情况。拼多多商家面对这样的情况又该如何处理呢&#xff1f;今天榆熙电商小编就跟大家聊一聊&#xff0c;在拼多多开店&#xff0c;春节期间合作物流不发货怎么办&#xff1f; 一、拼多多春节放…

Spring Security整合Spring Boot分布式认证

一、分布式系统认证技术分析 1、分布式系统认证 随着软件环境和需求的变化&#xff0c;软件的架构通常都会由单体结构演变成具有分布式架构的分布式系统。而分布式系统的每个服务都会有认证、授权的需求。如果每个服务都实现一套认证逻辑&#xff0c;就会非常冗余并且不现实。…

自定义异常及异常全局处理

目录【Java异常分类】【案例分析】【测试】【总结】【Java异常分类】 java中异常均继承自Throwable,其有两个重要的直接子类error与exception Error&#xff1a; 大部分是由虚拟机报出来的错误&#xff0c;是程序无法处理的错误,如 OutOfMemoryError,当JVM需要更多内存空间而…