Spring Security 6.x 图解身份认证的架构设计

简介: 【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现

spring_security_lg-1280x720.png

一、基本概念

“Authentication(认证)”是spring security框架中最重要的功能之一,所谓认证,就是对当前访问系统的用户给予一个合法的身份标识,用户只有通过认证后才可以进入系统,在物理世界中,有点类似“拿工卡刷门禁”的场景。

身份认证在市面上有很多种的实现协议,最常见的就是用户名密码的认证方式,另外还有OAuth2.0,CAS(Central Authentication Service),SAML等,其中OAuth2.0是一种我们比较熟悉的认证协议,例如微信,支付宝提供的第三方登录。

回到身份认证的原本需求:

  • 首先系统要提供对应的认证服务,即需要判断用户提交的凭证是否正确,凭证是一个比较宽泛的概念,密码只是其中一种,还包括短信验证码,指纹等,一切可以证明“你是你”的材料都可以是凭证
  • 在用户认证成功后,系统还要记录这些认证信息,并返回客户端一个令牌,对于后续的请求,通过这个令牌就可以校验是否经过认证,若已经完成过认证,那么应该取出当时认证的信息,包括用户名,权限等,然后继续执行后续的业务逻辑,若没有认证信息,则拒绝访问。这样才能对受保护的系统资源起到作用。

根据上面的描述,很自然地,我们想到定义一个controller的API接口来提供认证服务,然后定义一个“切面”来校验认证信息,这种方式可以方便地拦截到系统内各个资源的访问请求,不仅可以灵活配置,也不会侵入业务代码。

到此,我们对认证的架构有了一个初步的构想,先画一个简单的草稿

image.png

这里所谓的“令牌”,“凭证”,“认证信息”,“受保护资源”都是抽象的概念,并不特指某一种实现,“切面”也不是Spring的AOP,只表示在执行校验逻辑时,不与受保护资源相耦合,它应该是独立运作的模块。

下面具体看一下spring security中的认证架构设计,对比上图,学习一下spring security是如何实践的。

二、架构设计

spring security利用了SecurityFilterChain的过滤器中实现了校验逻辑,另外为了实现各种认证协议,spring security也内置了很多种认证实现类,供开发者直接使用,不过这里提供两种方式,一种也是利用SecurityFilterChain的过滤器来实现认证服务,当然也可以实现自定义的Controller来暴露API接口。

2.1 架构图

明确了上述两点之后,我们再给出spring security完整的认证架构,图中均以SecurityFilterChain的过滤器实现认证和校验的逻辑,这是比较常见惯用的方法。

可以参考官方文档 https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html,不过官方文档的结构组织比较散,这里我们再做一次整合,看起来更直观一些

image.png

接口:

  • Authentication:顶层接口,用于保存身份认证信息,主要包括3个部分:用户标识(principal,通常为用户名),凭证(credentials,通常为密码),权限信息(authorities,通常为该用户所拥有的角色)
  • SecurityContext:顶层接口,直译为安全上下文,内部只定义了getAuthentication和setAuthentication两个方法,概括地说,SecurityContext相当于用于装载Authentication对象的容器,在整个SecurityFilterChain中,为不同的认证机制操作Authentication对象时提供服务。
  • AuthenticationManager: 顶层接口,定义了“认证“方法,签名如下:
Authentication authenticate(Authentication authentication) throws AuthenticationException;
  • AuthenticationProvider:  顶层接口,同样也定义了一个签名相同的“认证”方法,不同于AuthenticationManager的认证方法,这个才是各种认证协议的具体实现,它通常接受一个未认证的Authentication对象的参数,该对象仅包含了principal和credentials的信息,在经过认证后,会把authorities填充进来,并将状态设置为已认证。在spring security中内置了很多实现类,例如OAuth2LoginAuthenticationProvider,用于实现OAuth2.0认证协议等。当然我们也可以根据需要自定义其实现。
  • SecurityContextRepository:顶层接口,定义了保存和加载SecuriyContext对象的方法,常用的实现有HttpSessionSecurityContextRepository,即通过request的会话对象session,存取SecurityContext的实例。
  • SecurityContextHolderStrategy:顶层接口,定义了在当前请求的线程中,获取和设置SecurityContext对象等方法,在5.8版本之后,新增了两个get/set“延迟(Deferred)”接口,主要是使用了Supplier函数式接口实现的惰性计算,不过只是性能上的考量,本质上都是用于维护SecurityContext对象的方法

类:

  • SecurityContextHolder:它是spring security认证模型中最为常用的一个工具类,它采用策略模式封装了SecurityContextHolderStrategy接口实现,默认的策略实现为ThreadLocalSecurityContextHolderStrategy,其底层使用了ThreadLocal实现对SecurityContext对象的存取逻辑,这样可以保证在一次请求的同一个线程中,方便地获取SecurityContext对象。
  • ProviderManager: AuthenticationManager的实现类,它内部维护了一个List成员变量,在实现AuthenticationManager#authenticate方法时,其实是遍历这个List列表,依次判断是否支持当前Authentication对象(如OAuth2LoginAuthenticationProvider支持OAuth2LoginAuthenticationToken),如果支持,则调用AuthenticationProvider#authenticate方法,完成认证过程。

2.2 业务流程

从图中可以看到,整个认证流程主要围绕以下3个Filter:

  1. SecurityContextHolderFilter:它在整个SecurityFilterChain中具有较高的优先级,因为当一个请求进入SecurityFilterChain的时候,需要从SecurityContextRepository加载SecurityContext实例①,并调用SecurityContextHolder对应的set方法进行保存②,以便后续其他地方获取这个SecurityContext实例,如上文所述,通常会保存在ThreadLocal中
  2. AuthorizationFilter:如果该请求没有被认证过,那么在当前的SecurityContext对象中是没有Authentication实例的,这时在执行AuthorizationFilter的逻辑时就会发生异常,AuthorizationFilter主要是用来判断请求访问受保护资源时,是否符合授权条件,而为了获取用户的授权信息,先通过SecurityContext得到Authentication认证信息①,这时如果获取到Authentication实例为空,就表示该请求并没有认证过,那么就会抛出一个AuthenticationCredentialsNotFoundException的异常②,这个异常会被ExceptionTranslationFilter捕获,通常情况下,异常处理方式就是跳转到到登录页面③,让用户完成登录的操作。
  3. AbstractAuthenticationProcessingFilter:它定义了一个比较通用的认证“模板”方法。当用户发起登录请求时,AbstractAuthenticationProcessingFilter配置的RequestMatcher就匹配到这次请求的url,默认执行认证的是UsernamePasswordAuthenticationFilter,它匹配的请求端点是"/login",此时它从request请求参数中获取用户名和密码,并封装成UsernamePasswordAuthenticationToken①,然后交给ProviderManager#authenticate方法对其认证②,在认证通过之后,我们将AuthenticationProvider返回的Authentication对象③,此时SecurityContextHolderStrategy会创建出一个空载的SecurityContext实例④,并传入上述Authentication⑤,然后调用SecurityContextHolderStrategy的保存方法⑤,最后通过SecurityContextRepository进行持久化⑦。

可以参考以下的样板代码,对于各类认证实现,基本上大同小异。

try {
    Authentication authenticationToken = createAuthentication() // // 例如创建UsernamePasswordAuthenticationToken,OAuth2AuthorizationCodeAuthenticationToken等等,将待认证的信息封装起来,
    Authentication authResult = this.authenticationManager.authenticate(someAuthenticationToken); // 交给ProviderManager进行认证,通常由实际的AuthenticationProvider实现类完成具体的认证逻辑,并将认证结果返回
    SecurityContext context = this.securityContextRepository.createEmptyContext(); // 创建一个空载的SecurityContext实例
    context.setAuthentication(authResult); // 传入经过认证的Authentication对象
    this.securityContextHolderStrategy.setContext(context); // 存储SecurityContextHolder中,方便同一个线程执行过程中的其他地方获取
    this.securityContextRepository.saveContext(context, request, response); // 进行持久化,方便下次请求访问时,可以获取对应SecurityContext,实现登录态的保持
    this.successHandler.onAuthenticationSuccess(request, response, authResult); // 认证成功后的流程,例如跳转到系统首页等
} catch (AuthenticationException ex) {
    // Authentication failed
    this.securityContextHolderStrategy.clearContext(); // 认证失败时,清空SecurityContext
    this.failureHandler.onAuthenticationFailure(request, response, failed); // 认证失败后的流程,例如提示错误信息等
}

说明:spring security对用户名和密码的认证提供了默认实现DaoAuthenticationProvider,但由于默认实现限制比较多,一般在实际的生产活动中不会采用,通常会继承AbstractUserDetailsAuthenticationProvider来定制开发,或者参考它的源码自定义实现AuthenticationProvider接口。

三、总结

最后,我们对spring security整个认证架构中的认证流程和存取校验流程,再做一个总结:

  • 认证流程:AuthenticationManager为这个系统所支持的所有认证协议,统一提供authenticate方法,比如支持用户名密码登录,也支持短信验证码,第三方授权登录等,不论哪种认证请求,最终都交由这个方法执行,其实现类ProviderManager则高度封装了认证过程,其中具体的AuthenticationProvider实现维护在List列表中,通过遍历使得不同的认证协议进入不同的认证实现类,然后都返回Authentication对象,Authentication定义了一个认证信息应该必须包含的信息,包括用户标识principal,凭证credentials,权限authorities,因此我们也可以实现自定义的AuthenticationProvider,并注册到ProviderManager中,然后再实现自定义的认证Filter和Authentication,这样就完成了整合。
  • 存取校验流程:在得到认证后的Authentication对象,需要解决的是如何获取这个Authentication对象,以判断该请求是否已经通过认证,这里就引入另一个重要的类SecurityContext,它相当于一个用于装载Authentication对象的容器,首先依赖SecurityContextRepository从持久化的介质(例如session)中加载出来SecurityContext对象,其次通过SecurityContextHolder内部策略类方便快速地读写SecurityContext对象,这里很容易就想到使用ThreadLocal来实现同一个请求的线程中存取操作,spring security也是这么做的,最终在得到SecurityContext后,可以通过其内部的Authentication对象判断是否已认证。

可见,上述两个核心流程基本围绕着Authentication和SecurityContext这两个接口来建设,前者对外提供认证服务,我们可以进行深度的定制开发,包括Authentication,Filter,AuthenticationProvider都可以自定义实现,并整合进入SecurityFilterChain,后者对内提供存取服务,通常情况下我们也不会对存取流程进行改造,对于绝大多数场景,只需要利用SecurityContextHolder这个工具类读写SecurityContext对象,这基本上已经足够了。这样的设计,在最大程度上固化了存取校验的逻辑,不会因为认证机制和结果的不同,而改变存取校验的逻辑。

image.png

相关文章
|
11月前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
1954 5
|
3月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
664 3
|
1月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
Spring Boot 3.x 微服务架构实战指南
|
5月前
|
JavaScript 前端开发 Java
垃圾分类管理系统基于 Spring Boot Vue 3 微服务架构实操指南
本文介绍了基于Java技术的垃圾分类管理系统开发方案与实施案例。系统采用前后端分离架构,后端使用Spring Boot框架搭配MySQL数据库,前端可选择Vue.js或Java Swing实现。核心功能模块包括垃圾分类查询、科普教育、回收预约等。文中提供了两个典型应用案例:彭湖花园小区使用的Swing桌面系统和基于Spring Boot+Vue的城市管理系统,分别满足不同场景需求。最新技术方案升级为微服务架构,整合Spring Cloud、Redis、Elasticsearch等技术,并采用Docker容器
356 0
|
2月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
769 1
|
2月前
|
Kubernetes Java 微服务
Spring Cloud 微服务架构技术解析与实践指南
本文档全面介绍 Spring Cloud 微服务架构的核心组件、设计理念和实现方案。作为构建分布式系统的综合工具箱,Spring Cloud 为微服务架构提供了服务发现、配置管理、负载均衡、熔断器等关键功能的标准化实现。本文将深入探讨其核心组件的工作原理、集成方式以及在实际项目中的最佳实践,帮助开发者构建高可用、可扩展的分布式系统。
408 0
|
4月前
|
存储 Java 数据库连接
简单学Spring Boot | 博客项目的三层架构重构
本案例通过采用三层架构(数据访问层、业务逻辑层、表现层)重构项目,解决了集中式开发导致的代码臃肿问题。各层职责清晰,结合依赖注入实现解耦,提升了系统的可维护性、可测试性和可扩展性,为后续接入真实数据库奠定基础。
404 0

热门文章

最新文章