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

推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文详细介绍了解决全栈跨域问题的方法及步骤,包括添加权限、设置Access-Control-Allow-Origin、白名单等。通过这些操作,可以实现在不同服务器上的数据访问,并解决后台报错问题。同时,还提供了解决second页面访问数据的方法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
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社区 版权所有