热门标签 | 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() 方法,设置到其中。

推荐阅读
  • 本文详细解析了Java面试中常见的问题及答案,旨在帮助求职者更好地准备面试,提高通过率。 ... [详细]
  • 本文深入探讨了网络编程中的基本概念,如指针、引用和可重入函数,并详细介绍了OSI七层模型和TCP/IP四层模型的功能与协议。同时,文章还对比了HTTP与HTTPS的区别,分析了HTTP请求报文的结构,讨论了TCP与UDP的主要差异,以及滑动窗口协议的工作原理。 ... [详细]
  • 本文探讨了在使用basicHttpBinding通过HTTPS发送请求时遇到的握手失败问题,分析了可能的原因及解决方案。 ... [详细]
  • Facebook PrestoDB 配置指南
    本指南详细介绍了如何安装和配置 Facebook PrestoDB,包括必要的文件设置和启动方法。 ... [详细]
  • 近期,公司在构建新的交易系统时遇到了一个常见的问题——金额存储。由于涉及资金的操作需要高度的准确性,使用float类型进行金额计算可能会导致不可预见的误差。本文将深入探讨这一问题,并提供解决方案。 ... [详细]
  • 本文详细介绍了如何通过微信H5网页授权机制获取用户的code,并进一步获取用户的基本信息,包括必要的配置步骤和前端代码实现。 ... [详细]
  • 本文探讨了在Git子模块目录中运行pre-commit时遇到的错误,并提供了一种通过Docker环境解决此问题的方法。 ... [详细]
  • 深入理解HTTP及TCP基础知识
    本文详细解析了TCP的三次握手与四次挥手过程,探讨了HTTP与HTTPS的区别及其特性,并深入讲解了HTTP缓存机制以及GET与POST请求的主要差异。 ... [详细]
  • 深入理解任意分频技术及其在FPGA中的应用
    本文探讨了FPGA中任意分频的重要性,特别是其在高频精确控制中的应用。文章不仅介绍了传统的分频方法,还详细阐述了一种基于DDS(直接数字合成)相位累加器的高精度任意分频技术,旨在为工程师和爱好者提供一种新的思路。 ... [详细]
  • 文章目录17、less17-UpdateQuery-Errorbased-String18、less18-HeaderInjection-ErrorBased-string19、l ... [详细]
  • Solr的安装与部署指南
    Solr作为一款独立的企业级搜索应用服务器,支持Web-service风格的API接口,允许用户通过HTTP请求提交XML文件以创建索引或执行搜索操作。本文将详细介绍Solr的安装步骤及配置方法。 ... [详细]
  • 本文详细探讨了Laravel框架中的数据库操作,包括读写分离、事务处理、Eloquent ORM的使用、关联关系管理及性能优化技巧。 ... [详细]
  • 在CentOS上构建Ntopng实时网络流量监控平台
    本文详细介绍了如何在CentOS操作系统上安装和配置Ntopng,一个强大的网络流量监控工具。Ntopng能够提供实时的网络流量分析,并通过Web界面展示详细的流量报告。 ... [详细]
  • 使用原生 AJAX 处理文件下载与 IE 浏览器图片显示问题
    本文探讨了如何利用原生 AJAX 实现文件的 POST 或 GET 请求下载,并监控下载状态。同时,针对 IE 浏览器及部分移动设备浏览器因路径问题导致图片无法正常显示的情况,提供了有效的解决方案。 ... [详细]
  • 尝试按照官方文档步骤配置示例应用时遇到注册失败的问题。 ... [详细]
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社区 版权所有