热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

走近科学之探秘SpringSecurity的核心组件

1.SecurityContextHolder类SecurityContextHolder顾名思义,他是一个holder,用于持有的是安全上下文
1. SecurityContextHolder 类

SecurityContextHolder 顾名思义,他是一个 holder,用于持有的是安全上下文(security context)的信息。SecurityContextHolder 记录如下信息:当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色或权限等等。

Tomcat 建立会话的流程

在典型的 web 应用程序中,用户登录一次,然后由其会话 ID 标识。服务器缓存持续时间会话的主体信息。有人可能对 Tomcat 建立会话的流程还不熟悉,这里稍微整理一下。

当客户一般是认证成功后,在调用 request.getSession() 方法后,Tomcat 会创建一个 HttpSesion 对象,存入一个 ConcurrentHashMap,Key 是 SessionId,Value 就是 HttpSession。然后请求完成后,在返回的报文中添加 Set-COOKIE:JSESSIONID=xxx,然后客户端浏览器会保存这个 COOKIE。当浏览器再次访问这个服务器的时候,都会带上这个 COOKIE。Tomcat 接收到这个请求后,根据 JSESSIONID 把对应的 HttpSession 对象取出来,放入 HttpSerlvetRequest 对象里面。


  1. 这些处理都在 Spring Security 的拦截链之前完成。
  2. Tomcat 中 HttpSesion 的默认过期时间为 30 分钟。
  3. 如无特殊处理,COOKIE JSESSIONID 会在浏览器关闭的时候清除。
  4. HttpSession 会一直存在服务端,实际上是存在运行内存中。除非 Session 过期 或者 Tomcat 奔溃 或者 服务器奔溃,否则会话信息不会消失。

Spring Security 会话存储流程

SecurityContext 对象实际存储于 Tomcat HttpSession 中的一个 key 中,名为 “SPRING_SECURITY_CONTEXT”。

在 Spring Security 中,在请求之间存储 SecurityContext 的责任落在 SecurityContextPersistenceFilter 上,默认情况下,该上下文将上下文存储为 HTTP 请求之间的HttpSession 属性。SecurityContextPersistenceFilter 是 Security 的拦截器,而且是拦截链中的第一个拦截器,请求来临时它会从 HttpSession 中把 SecurityContext 取出来,然后放入 SecurityContextHolder。在所有拦截器都处理完成后,再把 SecurityContext 存入 HttpSession,并清除 SecurityContextHolder 内的引用。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;if (request.getAttribute(FILTER_APPLIED) != null) {// ensure that filter is only applied once per requestchain.doFilter(request, response);return;}final boolean debug = logger.isDebugEnabled();request.setAttribute(FILTER_APPLIED, Boolean.TRUE);if (forceEagerSessionCreation) {HttpSession session = request.getSession();if (debug && session.isNew()) {logger.debug("Eagerly created session: " + session.getId());}}HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response);// 利用HttpSecurityContextRepository从HttpSesion中获取SecurityContext对象// 如果没有HttpSession,即浏览器第一次访问服务器,还没有产生会话。// 它会创建一个空的SecurityContext对象SecurityContext contextBeforeChainExecution = repo.loadContext(holder);try {// 把SecurityContext放入到SecurityContextHolder中SecurityContextHolder.setContext(contextBeforeChainExecution);// 执行拦截链,这个链会逐层向下执行chain.doFilter(holder.getRequest(), holder.getResponse());}finally { // 当拦截器都执行完的时候把当前线程对应的SecurityContext从SecurityContextHolder中取出来SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();// Crucial removal of SecurityContextHolder contents - do this before anything// else.SecurityContextHolder.clearContext();// 利用HttpSecurityContextRepository把SecurityContext写入HttpSessionrepo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());request.removeAttribute(FILTER_APPLIED);if (debug) {logger.debug("SecurityContextHolder now cleared, as request processing completed");}}}

SecurityContextHolder 可以用来设置和获取 SecurityContext。它主要是给框架内部使用的,可以利用它获取当前用户的 SecurityContext 进行请求检查,和访问控制等。

SecurityContextHolder 存储策略


  1. 存储在线程中。
  2. 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
  3. 在所有线程中都相同。

SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。在 web 环境下,Spring Security 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。

SecurityContextHolder 采用策略模式,根据 strategyName 字段创建不同的 SecurityContextHolderStrategy 对象。

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";// System.getProperty() 从JVM中获取配置的属性SYSTEM_PROPERTY// 获取不到 strategyName = nullprivate static String strategyName = System.getProperty(SYSTEM_PROPERTY);private static SecurityContextHolderStrategy strategy;private static int initializeCount = 0;// 随着类的加载而加载static {initialize();}...// 初始化private static void initialize() {if (!StringUtils.hasText(strategyName)) {// 设置默认策略strategyName = MODE_THREADLOCAL;}// 根据strategyName字段创建对应的SecurityContextHolderStrategy对象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 {// 自定义策略...}initializeCount++;}// public static void setContext(SecurityContext context) {strategy.setContext(context);}// 可以设置新的存储策略public static void setStrategyName(String strategyName) {SecurityContextHolder.strategyName = strategyName;// 修改strategyName后需要重新执行initialize创建新的SecurityContextHolderStrategy对象initialize();}...
}

2. SecurityContext 接口

SecurityContext 安全上下文,用户通过 Spring Security 的校验之后,验证信息存储在 SecurityContext 中,SecurityContext 接口只定义了两个方法,实际上其主要作用就是设置、获取 Authentication 对象。


public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);
}

3. Authentication 接口

Authentication 直译过来是“认证”的意思,在 Spring Security 中 Authentication 用来表示当前用户是谁,一般来讲你可以理解为 authentication 就是一组用户名密码信息。

Authentication 内容


  • principal:用于标识用户当通过 username 和 password 认证用户时,principal 通常是一个 UserDetails 的实现类对象。
  • credentials:通常是密码,在很多场景,如果用户已经被认证,那么此项将被清除以防止密码泄露。
  • authorities:用户具有的权限或角色。

public interface Authentication extends Principal, Serializable {// 权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。Collection getAuthorities();// 密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全Object getCredentials();// 细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,// 它记录了访问者的ip地址和sessionId的值。Object getDetails();// 最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

4. GrantedAuthority 接口

GrantedAuthority 是在 Authentication 的接口中使用集合存储权限。

Collection getAuthorities();

可以看到权限集合存放的元素是 GrantedAuthority 的实现类,也可以使用 String。

该接口表示了当前用户所拥有的权限(或者角色)信息。这些信息有授权负责对象 AccessDecisionManager 来使用,并决定最终用户是否可以访问某资源。

5. UserDetails 接口

这个接口规范了用户详细信息所拥有的字段,譬如用户名、密码、账号是否过期、是否锁定等。在 Spring Security 中,获取当前登录的用户的信息,一般情况是需要在这个接口上面进行扩展,用来对接自己系统的用户。

public interface UserDetails extends Serializable {Collection getAuthorities();String getPassword();String getUsername();// 用户账户是否过去,过期的用户不能被认证boolean isAccountNonExpired();// 用户是否被lock,lock的用户不能被认证boolean isAccountNonLocked();// 用户的credentials (password)是否过期,国企的不能认证成功boolean isCredentialsNonExpired();// 用户是enabled或者disabled,diabled的用户不能被认证boolean isEnabled();
}

6. UserDetailsService 接口

这个接口非常重要,一般情况我们都是通过扩展这个接口来显示获取我们的用户信息,用户登录时传递的用户名和密码也是通过这里这查找出来的用户名和密码进行校验。

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

但是真正的校验不在这里,而是由 AuthenticationManager 以及 AuthenticationProvider 负责的,需要强调的是,如果用户不存在,不应返回 NULL,而要抛出异常 UsernameNotFoundException。

7. AuthenticationManager 接口

AuthenticationManager 是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录。

public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

所以说 AuthenticationManager 一般不直接认证,AuthenticationManager 接口的常用实现类 ProviderManager 内部会维护一个 List 列表,存放多种认证方式,实际上这是委派器模式的应用(Delegate)。

核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式有:用户名+密码,邮箱+密码,手机号码+密码登录,分别对应了三个 AuthenticationProvider。

8. AuthenticationProvider 接口

AuthenticationProvider 接口最常用的一个实现便是 DaoAuthenticationProvider。

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication) throws AuthenticationException;boolean supports(Class authentication);
}

顾名思义,Dao 正是数据访问层的缩写,也暗示了这个身份认证器的实现思路。主要作用:它获取用户提交的用户名和密码,比对其正确性,如果正确,返回一个数据库中的用户信息(假设用户信息被保存在数据库中)。

9. AuthenticationProvider 和 UserDetailsService 关系


  1. UserDetails 接口代表了最详细的用户信息,这个接口包含了一些必要的用户信息字段,我们一般都需要对它进行必要的扩展。它和 Authentication 接口很类似,比如它们都拥有 username,authorities。
  2. Authentication 的 getCredentials() 与 UserDetails 中的 getPassword() 需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。
  3. Authentication 中的 getAuthorities() 实际是由 UserDetails 的 getAuthorities() 传递而形成的。还记得 Authentication 接口中的 getUserDetails() 方法吗?其中的 UserDetails 用户详细信息便是经过了 AuthenticationProvider 之后被填充的。
  4. UserDetailsService 和 AuthenticationProvider 两者的职责常常被人们搞混,UserDetailsService 只负责从特定的地方加载用户信息,可以是数据库、redis缓存、接口等

10. Spring Security是如何完成身份认证的?


  1. 用户名和密码被过滤器获取到,封装成 Authentication,通常情况下是 UsernamePasswordAuthenticationToken 实现类。
  2. AuthenticationManager 身份管理器负责验证这个 Authentication。
  3. 认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
  4. SecurityContextHolder 安全上下文容器将第 3 步填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication() 方法,设置到其中。

推荐阅读
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 本文探讨了如何在 PHP 的 Eloquent ORM 中实现数据表之间的关联查询,并通过具体示例详细解释了如何将关联数据嵌入到查询结果中。这不仅提高了数据查询的效率,还简化了代码逻辑。 ... [详细]
  • 深入解析 Spring Security 用户认证机制
    本文将详细介绍 Spring Security 中用户登录认证的核心流程,重点分析 AbstractAuthenticationProcessingFilter 和 AuthenticationManager 的工作原理。通过理解这些组件的实现,读者可以更好地掌握 Spring Security 的认证机制。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 探讨了如何解决Ajax请求响应时间过长的问题。本文分析了一个从服务器获取少量数据的Ajax请求,尽管服务器已经对JSON响应进行了缓存,但实际响应时间仍然不稳定。 ... [详细]
author-avatar
手机用户2502905797
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有