SpringSecurity 深挖源码–请求间认证信息共享


SpringSecurity 深挖源码–请求间认证信息共享


其实从这篇开始是近期我在整理的内容,因为之前我们用的一套系统是基于spring-security-oauth实现的统一身份认证, 而Spring Security官方已经明确该项目已过期,因此近期在做底层代码升级,其中有不少坑,希望记录下来给有需要的小伙伴。

请求间共享用户

一般认证成功后的用户信息是通过 Session 在多个请求之间共享,那么Spring Security中是如何实现将已认证的用户信息对象 Authentication 与 Session 绑定的进行具体分析。

认证过程

spring 官方文档中描述认证的主要部分包括以下几部分:

  • SecurityContextHolder – SecurityContextHolderSpring Security在此存储经过身份验证的人员的详细信息。
  • SecurityContext-从中获取,SecurityContextHolder并包含Authentication当前经过身份验证的用户的。
  • 身份验证-可以作为输入,AuthenticationManager以提供用户提供的用于进行身份验证的凭据,或提供来自的当前用户SecurityContext
  • GrantedAuthority-在Authentication(例如角色,范围等)上授予委托人的权限
  • AuthenticationManager-定义Spring Security的Filters如何执行身份验证的API 。
  • ProviderManager-的最常见实现AuthenticationManager
  • AuthenticationProvider-用于ProviderManager执行特定类型的身份验证。
  • 具有AuthenticationEntryPoint以下条件的请求凭证-用于从客户端请求凭证(即,重定向到登录页面,发送WWW-Authenticate响应等)。
  • AbstractAuthenticationProcessingFilter-Filter用于认证的基础。这也为高级别的身份验证流程以及各个部分如何协同工作提供了一个好主意。
SpringSecurity 深挖源码--请求间认证信息共享
image-20210428190237856

认证成功后的处理:

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);//将认证信息保存SecurityContext
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

SecurityContextHolder

SecurityContextHolder中是对SecurityContext的处理,而SecurityContext存在一个默认实现类SecurityContextImpl,该类是是对Authentication的封装:

public class SecurityContextImpl implements SecurityContext {
    private static final long serialVersionUID = 520L;
    private Authentication authentication;
    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }
    ...
}

SecurityContextHolder是通过ThreadLocal存储SecurityContext对象:

public class SecurityContextHolder {
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty("spring.security.strategy");
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;
    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            strategyName = "MODE_THREADLOCAL";//默认为THREADLOCAL模式
        }

        if (strategyName.equals("MODE_THREADLOCAL")) {
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }

        ++initializeCount;
    }
    
    static {//随着类初始化而运行从而初始化SecurityContextHolder
        initialize();
    }

注意:

补充说明:InheritableThreadLocal 与 ThreadLocal的区别

ThreadLocal , 存储变量只能被当前线程使用

InheritableThreadLocal , 父线程中存储的变量子线程也可使用

***ThreadLocal是以弱引用的方式保存对象的,关于ThreadLocal下一篇文章会专门整理。***

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter是整个过滤器链中的第一个过滤器,它主要做两件事:

  1. 判断session中是否有认证信息,如果有则将认证信息加载至SecurityContextHolder,后续请求不需要身份认证;
  2. 当响应完成后返回时,将SecurityContextHolder中取出认证信息,并存入session中,同时释放SecurityContextHolder中的认证信息**(这步是避免内存泄露)**
   HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);//判断session中是否存在认证信息,如果存在则取出加载至SecurityContext,如果不存在则创建一个空的SecurityContext
            boolean var13 = false;

            try {
                var13 = true;
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                chain.doFilter(holder.getRequest(), holder.getResponse());//继续后续的过滤器
                var13 = false;
            } finally {
                if (var13) {
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    SecurityContextHolder.clearContext();
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if (debug) {
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }
   //响应完成后 取出SecurityContext
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            //清楚SecurityContextHolder中的SecurityContext
            SecurityContextHolder.clearContext();
            //放入session
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            if (debug) {
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }

获取用户

由于用户认证信息保存在SecurityContextHolder中,因此获取用户信息比较简单

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

默认情况下,会SecurityContextHolder使用ThreadLocal来存储这些详细信息,这意味着SecurityContext,即使SecurityContext并未将作为显式传递给这些方法的参数,也始终可将其用于同一线程中的方法。ThreadLocal如果在处理了当前委托人的请求之后要清除线程,则以这种方式使用是非常安全的。Spring Security的FilterChainProxy确保SecurityContext始终清除。

某些应用程序不完全适合使用ThreadLocal,因为它们使用线程的特定方式。例如,Swing客户端可能希望Java虚拟机中的所有线程都使用相同的安全上下文。SecurityContextHolder可以在启动时配置策略,以指定希望如何存储上下文。对于独立应用程序,您将使用该SecurityContextHolder.MODE_GLOBAL策略。其他应用程序可能希望让安全线程产生的线程也采用相同的安全身份。这是通过使用来实现的SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。您可以通过SecurityContextHolder.MODE_THREADLOCAL两种方式从默认模式更改模式。第一个是设置系统属性,第二个是在上调用静态方法SecurityContextHolder。大多数应用程序不需要更改默认值,但是如果需要更改,请查看JavaDocSecurityContextHolder 了解更多。


原文始发于微信公众号(云户):SpringSecurity 深挖源码–请求间认证信息共享

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/25771.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!