Spring Security OAuth2.0 实现分布式系统的认证和授权

Spring Security OAuth2.0 实现分布式系统的认证和授权

  • 1. 基本概念
    • 1.1 什么是认证?
    • 1.2 什么是会话?
      • 1.2.1 基于 session 的认证方式
      • 1.2.2 基于 token 的认证方式
    • 1.3 什么是授权?
      • 1.3.1 授权的数据模型
    • 1.4 RBAC 介绍
  • 2. Spring Security
    • 2.1 Spring Security 介绍
    • 2.2 Spring Security 工作原理
      • 2.2.1 结构总览
      • 2.2.2 认证的流程
        • 2.2.2.1 认证流程
        • 2.2.2.2 AuthenticationProvider 介绍
        • 2.2.2.3 UserDetailsService 介绍
        • 2.2.2.4 PasswordEncoder 介绍
      • 2.2.3 授权流程
        • 2.2.3.1 授权流程
        • 2.2.3.2 授权决策
      • 2.2.4 会话
      • 2.2.5 授权
        • 2.2.5.1 web授权
        • 2.2.5.2 方法授权
  • 3.分布式系统认证方案
    • 3.1 什么是分布式系统
    • 3.2 分布式认证需求
    • 3.3 分布式认证方案
      • 3.3.1 选型分析
      • 3.3.2 技术方案
  • 4. OAuth 2.0
    • 4.1 OAuth2.0 介绍
    • 4.2 Spring Cloud Security OAuth 2.0
    • 4.2.1 环境介绍
  • 5.Spring Security OAuth2.0 实现分布式系统认证和授权示例源码
  • 参考文献

1. 基本概念

1.1 什么是认证?

为什么要认证

认证是为了保护系统的隐私数据和资源。

什么是认证

判断用户的身份是否合法的过程。

常见的认证方式有哪些

  • 用户名密码登录
  • 二维码登录
  • 手机短信登录
  • 指纹认证

1.2 什么是会话?

什么是会话

会话就是系统为了保持当前用户的登录状态所提供的机制。

常见的会话机制

  • 基于 session 的方式
  • 基于 token 的方式

两种实现方式的对比

基于 session 的认证方式由 Servlet 规范定制,服务器要存储 session 信息要占用内存资源,客户端需要支持 cookie;
基于 token 的认证方式则一般不需要服务器存储 token,并且不限制客户端的存储方式。

如今移动互联网时代更多类型的客户端需要接入系统,系统多是采用前后端分离的架构实现,所以基于 token 的方式更适合。

1.2.1 基于 session 的认证方式

基于 session 的认证方式如下图
Spring Security OAuth2.0 实现分布式系统的认证和授权_第1张图片

基于 session 的认证交互流程

用户认证成功后,在服务端生成用户相关的数据保存在 session(当前会话) 中,发给客户端 的 session_id 存放到 cookie 中, 这样用户客户端请求时带上 session_id 就可以验证服务端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或 session 过期销毁时,客户端的 session_id 也就无效了。

1.2.2 基于 token 的认证方式

基于 token 的认证方式如下图
Spring Security OAuth2.0 实现分布式系统的认证和授权_第2张图片

基于 token 的认证交互流程

用户认证成功后,服务端生成一个 token 发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到 token 通过校验后即可确认用户身份。

1.3 什么是授权?

为什么要授权

是为了更细粒度的对隐私数据进行划分。

什么是授权

授权是用户认证通过根据用户权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

1.3.1 授权的数据模型

授权可以简单的理解为 Who 对 What(Which) 进行 How 操作。

关键的概念

  • Who,即主体(Subject):主体一般是用户,也可以是程序,需要访问系统中的资源。
  • What,即资源(Resource):系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息等。系统菜单、页面、按钮、代码方法都属于系统功能资源,对于web系统每个功能资源通常对应一个URL;系统商品信息、系统订单信息都属于实体资源(数据资源),实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号为 001 的商品为资源实例。
  • How,权限/许可(Permission):规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为 001 的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。

主体、资源、权限关系如下图
Spring Security OAuth2.0 实现分布式系统的认证和授权_第3张图片

主体、角色、权限关系如下图
Spring Security OAuth2.0 实现分布式系统的认证和授权_第4张图片

1.4 RBAC 介绍

如何实现授权?

业界通常基于 RBAC 的实现授权。

基于角色的访问控制

RBAC 基于角色的访问控制 (Role Based Access Control) 是按角色授权。

例如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
Spring Security OAuth2.0 实现分布式系统的认证和授权_第5张图片

当需要修改角色的权限时就需要修改授权相关的代码,系统的可扩展性差。

基于资源的访问控制

RBAC 基于资源的访问控制 (Resource Based Access Control) 是按资源(或权限)进行授权。
Spring Security OAuth2.0 实现分布式系统的认证和授权_第6张图片

优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统的可扩展性强。

2. Spring Security

2.1 Spring Security 介绍

Spring Security 是一个能够为基于 Spring 的企业级应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是 Spring 生态系统中的一员,因此它伴随整个 Spring 生态系统不断修正、升级,在 Spring Boot 项目中加入 Spring Security 更是十分简单,使用 Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。

2.2 Spring Security 工作原理

2.2.1 结构总览

Spring Security 所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。一般实现的方式有 Filter 和 AOP。Spring Security 对 web 资源的保护是靠 Filter 实现的。

当初始化 Spring Security 时,会创建一个名为 SpringSecurityFilterChain 的 Servlet 过滤器,类型为 org.springframework.security.web.FilterChainProxy, 它实现了 javax.servlet.Filter, 因此外部的请求会经过此类。

Spring Security 过滤器链结构
Spring Security OAuth2.0 实现分布式系统的认证和授权_第7张图片

  • AuthenticationManager:用于用户认证
  • AccessDecisionManager:用于权限校验

FilterChainProxy 是一个代理,真正起作用的是 FilterChainProxy 中 SecurityFilterChain 所包含的各个 Filter,同时这些 Filter 作为 bean 被 Spring 管理,它们是 Spring Security 的核心,各有各的职责,但它们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器 (AuthenticationManager)和决策管理器 (AccessDecisionManager) 进行处理。

FilterChainProxy相关类的UML
Spring Security OAuth2.0 实现分布式系统的认证和授权_第8张图片

Spring Security 功能的实现主要是由一系列的过滤器链相互配合完成
Spring Security OAuth2.0 实现分布式系统的认证和授权_第9张图片

  • SecurityContextPersistenceFilter:这个 Filter 是整个拦截过程的入口和出口 (也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 SecurityContextHolder 所持有的 SecurityContext。
  • UsernamePasswordAuthenticationFilter:用于处理来自表单提交的认证,该表单必须提供对应的用户名和密码。其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关的改变。
  • FilterSecurityInterceptor: 是用于保护 web 资源的,使用 AccessDecisionManager 对当前用户进行授权访问。
  • ExceptionTranslationFilter:能够捕获来自 FilterChain 的所有异常,并进行处理,但是它只会处理两类异常,AuthenticationException 和 AccessDecisionException,其他的异常它会继续抛出。

2.2.2 认证的流程

2.2.2.1 认证流程

Spring Security OAuth2.0 实现分布式系统的认证和授权_第10张图片

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

AuthenticationManager 接口(认证管理器) 是认证相关的核心接口,也发起认证的出发点,他的实现类为 ProviderManager。而 Spring Security 支持多种认证方式,因此 ProviderManager 维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider 完成的。

Web 表单的对应的 AuthenticationProvider 实现类为 DaoAuthenticationProvider, 它的内部又维护着一个 UserDetailsService 负责 UserDetails 的获取。最终 AuthenticationProvider 将 UserDetails 填充至 Authentication。

认证核心组件的大体关系
Spring Security OAuth2.0 实现分布式系统的认证和授权_第11张图片

2.2.2.2 AuthenticationProvider 介绍

AuthenticationProvider 处理了认证的逻辑,它会去比对 UserDetailsService 提取到的用户密码和用户提供的密码是否一致。认证通过会将 Authentication(UsernamePasswordAuthenticationToken实现) 返回。

2.2.2.3 UserDetailsService 介绍

UserDetailsService 负责根据用户名提取用户信息 UserDetails(包含密码)
可以通过将自定义的 UserDetailsService 公开为 spring bean 来定义自定义身份验证。

2.2.2.4 PasswordEncoder 介绍

常见的 PasswordEncoder

  • BCryptPasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder

2.2.3 授权流程

2.2.3.1 授权流程

Spring Security 可以通过 http.authorizeRequests() 对 web 请求进行授权保护。Spring Security 使用标准 Filter 建立了对 web 请求的拦截,最终实现对资源的授权访问。

Spring Security 的授权流程
Spring Security OAuth2.0 实现分布式系统的认证和授权_第12张图片

授权相关主要的过滤器

  • FilterSecurityInterceptor: 获取资源所需要的权限、用户所具有的权限
  • SecurityMetadataSource:
  • AccessDecisionManager: 对比获取资源所需要的权限和用户所具有的权限

授权流程

  1. 拦截请求:已认证用户访问受保护的web资源将被SecurityFilterChain中的FilterSecurityInterceptor 的子类拦截。
  2. 获取资源访问策略:FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类 DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限 Collection
  3. 最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。
2.2.3.2 授权决策

AccessDecisionManager 采用投票的方式来确定是否能够访问受保护资源的权限。
Spring Security OAuth2.0 实现分布式系统的认证和授权_第13张图片

SpringSecurity 内置了三个基于投票的 AccessDecisionManager实现类, 默认使用 AffirmativeBased

  • AffirmativeBased
  • ConsensusBased
  • UnanimousBased

AffirmativeBased 执行逻辑

  1. 只要 AccessDecisionVoter的投票为 ACCESS_GRANTED 则同意用户进行访问;
  2. 如果全部弃权,也表示通过;
  3. 如果没有一个人投赞成票,但是有人投反对票,则将抛出 AccessDeniedException.

ConsensusBased 执行逻辑

  1. 如果赞成票多余反对票则表示通过;
  2. 反过来,如果反对票多于赞成票则将抛出 AccessDeniedException;
  3. 如果赞成票与反对票相同且不等于 0,并且属性 allowIfEqualGrantedDeniedDecisions 的值为 true,则表示通过,否则将抛出异常 AccessDeniedException。参数 allowIfEqualGrantedDeniedDecisions 默认值为 true;
  4. 如果所有的 AccessDecisionVoter 都弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,如果该值为true则表示通过,否则将抛出异常 AccessDecisionException,参数 allowIfAllAbstainDecisions 的值默认为 false.

UnanimousBased 执行逻辑

  1. 如果受保护对象配置的某一个 ConfigAttribute 被任意的 AccessDecisionVoter 反对了,则将抛出 AccessDeniedException。
  2. 如果没有反对票,但是有赞成票,则表示通过。
  3. 如果全部弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,true 则通过,false 则抛出 AccessDeniedException。

Spring Security 也内置了一些投票者实现类

  • RoleVoter
  • AuthenticatedVoter
  • WebExpressionVoter

2.2.4 会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。

Spring Security 提供会话管理,认证通过后将身份信息放入 SecurityContextHolder 上下文,SecurityContext 与当前线程进行绑定,方便用户获取用户身份。

会话机制

  • always:如果没有 session 存在就创建一个
  • ifRequired:如果需要就创建一个 Session (默认) 登录时。
  • never:Spring Security 将不会创建 session, 但是如果应用中其他地方创建了 session ,那么Spring Security 将会使用它。
  • stateless:Spring Security 将绝对不会创建 session,也不会使用session

默认情况下,Spring Security 会为每个登录成功的用户新建一个 session, 就是 IfRequired

stateless 适用于 Rest API 及其无状态认证机制。

安全会话cookie

  • httpOnly: 如果为 true,浏览器脚本将无法访问 cookie
  • secure: 如果为 true, 则 cookie 将仅通过 HTTPS 连接发送

2.2.5 授权

授权的方式包括 web 授权和方法授权,web 授权是通过 url 拦截进行实现,方法授权是通过方法拦截进行授权。他们都会调用 accessDecisionManager 进行授权决策,若为 web 授权则拦截器为 FilterSecurityInterceptor;若为方法授权则拦截器为 MethodSecurityInterceptor。如果同时通过 web 授权和方法授权则先执行 web 授权,执行方法授权。最后决策通过,则允许访问资源,否则将禁止访问。
Spring Security OAuth2.0 实现分布式系统的认证和授权_第14张图片

授权方式

  • web 授权:通过 url 拦截授权
  • 方法授权: 通过方法拦截授权
2.2.5.1 web授权

推荐使用基于资源的授权。
配置权限拦截时,应该将具体的权限拦截放到正则表达式的拦截前。

http.authorizeRequests() 保护 URL 常用的方法

  • authenticated() 保护 URL,需要用户登录
  • permitAll() 指定 URL 无需保护,一般应用与静态资源文件
  • hasRole(String role) 限制单个角色访问,角色将被增加 “ROLE_”,所以 “ADMIN” 将和 “ROLE_ADMIN” 进行比较。
  • hasAuthority(String authority) 限制单个权限访问
  • hasAnyRole(String… roles) 允许多个角色访问
  • hasAnyAuthority(String… authorities) 允许多个权限访问
  • access(String attribute) 该方法使用 SpEL 表达式,所以可以创建复杂的限制
  • haslpAddress(String ipaddressExpression) 限制IP地址或子网
2.2.5.2 方法授权

从 Spring Security 2.0 版本开始,它支持服务层方法的安全性的支持

  • @PreAuthorize 方法执行前拦截
  • @PostAuthorize 方法执行后拦截
  • @Secured

可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。

@Secured 使用

  • @Secured(“IS_AUTHENTICATED_ANONYMOUSLY”): 不需要登录就可以访问
  • @Secured(“ROLE_TELLER”):需要角色为 TELLER 才可以访问

@PreAuthorize 使用

  • @PreAuthorize(“hasAuthority(‘p1’)”):拥有p1 权限才可以访问

3.分布式系统认证方案

3.1 什么是分布式系统

随着软件环境和需求的变化,软件的架构由单体结构演变为分布式架构,具有分布式架构的系统叫分布式系统。

分布式系统的运行通常依赖网络,它将单体结构的系统分为若干服务,服务之间通过网路交互来完成用户的业务,当前流行的微服务架构就是分布式系统架构。

分布式系统架构
Spring Security OAuth2.0 实现分布式系统的认证和授权_第15张图片

分布式系统的特点

  1. 分布性:每个部分都可以独立部署,服务之间交互通过网络进行通信。
  2. 伸缩性:每个部分都可以集群方式部署,并可针对部分节点进行硬件及软件扩容,具有一定的伸缩能力
  3. 共享性:每个部分都可以作为共享资源对外提供服务,多个部分可能有操作共享资源的情况。
  4. 开放性:每个部分根据需求都可以对外发布共享资源的访问接口,并可允许第三方系统访问。

3.2 分布式认证需求

分布式系统的每个服务都会有认证、授权的需求,如果每个服务都实现一套认证授权逻辑会非常冗余,考虑分布式系统共享性的特点,需要由独立的认证服务处理系统认证授权的请求;考虑分布式系统开放性的特点,不仅对系统内部服务提供认证,对第三方系统也要提供认证。

分布式认证的需求总结:

  • 统一认证授权
  • 应用接入认证

统一认证授权

提供独立的认证服务,统一处理认证授权。
无论是不同类型的用户,还是不同种类的客户端(web端、H5、APP),均采用一致的认证、权限、会话机制,实现统一认证授权。
要实现统一则认证方式必须可扩展,支持各种认证需求,比如:用户名密码认证、短信验证码、二维码、人脸识别等认证方式,并可以非常灵活的切换。

应用接入认证

应提供扩展和开放的能力,提供安全的系统对接机制,并可开放部分 API 给接入第三方使用,一方应用(内部系统服务)和三方应用(第三方应用)均采用统一机制接入。

3.3 分布式认证方案

3.3.1 选型分析

1.基于 Session 的认证方式

  • Session 复制
  • Session 黏贴
  • Session 集中存储

总的来说,基于 session 认证的认证方式,可以更好的在服务端对会话进行控制,且安全性较高,但是 session 机制方式基于 cookie,在复杂多样的移动客户端上不能有效的使用,并且无法跨域,另外随着系统的扩展需提高 session 的复制、黏贴及存储的容错性。

2.基于token的认证方式

基于 token 的认证方式,服务端不用存储认证数据,易维护扩展性强,客户端可以把token存在任意地方,并且可以实现 web 和 app 统一认证机制。其缺点也很明显,token 由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽,另外,token的签名操作也会给 cpu 带来额外的处理负担。

3.3.2 技术方案

根据选型的分析,决定采用基于 token 的认证方式,它的优点是:

  1. 适合统一认证的机制,客户端、一方应用、三方应用都遵循一致的认证机制。
  2. token 认证方式对第三方应用的接入更适合,因为它更开放,可使用当前有流行的开放协议 Oauth 2.0 、JWT 等。
  3. 一般情况下服务端无需存储会话信息,减轻了服务器的压力。

分布式系统认证技术方案
Spring Security OAuth2.0 实现分布式系统的认证和授权_第16张图片

分布式系统认证流程描述

  1. 用户通过接入方(应用)登录,接入方采取 OAuth2.0 方式在统一认证服务中认证
  2. 认证服务调用验证该用户的身份是否合法,并获取用户权限信息。
  3. 认证服务获取接入方权限信息,并验证接入方是否合法。
  4. 若登录用户以及接入方都合法,认证服务生成 jwt 令牌返回给接入方,其中 jwt 中包含了用户权限及接入方权限。
  5. 后续,接入方携带 jwt 令牌通过网关对微服务资源进行访问。
  6. 网关对令牌解析、并验证接入方的权限是否能够访问本次请求的微服务。
  7. 如果接入方的权限没问题,网关将原请求header中附加解析后的明文Token,并将请求转发至微服务。
  8. 微服务收到请求,明文token中包含登录用户的身份和权限信息。因此后续微服务自己可以干两件事:1,用户授权拦截(看当前用户是否有权访问该资源)2,将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)

流程所涉及到认证服务、网关这两个组件职责如下:

  1. 统一认证服务:它承载了OAuth2.0接入方认证、登入用户的认证、授权以及生成令牌的职责,完成实际的用户认证、授权功能。
  2. 网关:作为系统的唯一入口,网关为接入方提供定制的API集合,它可能还具有其它职责,如身份验证、监控、负载均衡、缓存等。网关方式的核心要点是,所有的接入方和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。

4. OAuth 2.0

4.1 OAuth2.0 介绍

OAuth (开放授权) 是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。

OAuth 2.0 是 OAuth 协议的延续版本,但不向后兼容 OAuth 1.0,即完全废止 OAuth 1.0。

OAuth2.0 认证过程
Spring Security OAuth2.0 实现分布式系统的认证和授权_第17张图片

OAuth 2.0 角色

  1. 客户端:本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源。
  2. 资源拥有者:通常为用户,也可以是应用程序,即该资源的拥有者。
  3. 授权服务器:用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌,作为客户端访问资源服务器的凭据。
  4. 资源服务器:存储资源的服务器。

现在还有一个问题,服务提供商能允许随便一个客户端就接入到他们的授权服务器吗?答案是否定的,服务提供商会给准入的接入方一个身份,用于接入时的凭据:

  • client_id: 客户端标识
  • client_secret: 客户端密钥

因此准确的来说,授权服务器对两种 OAuth2.0 中的两个角色进行认证授权,分别是 资源拥有者、客户端。

4.2 Spring Cloud Security OAuth 2.0

4.2.1 环境介绍

Spring-Security-OAuth2.0 是对 OAuth2.0 的一种实现,并且和 Spring Security 相辅相成,与 Spring Cloud 体系的集成也非常便利。

OAuth 2.0 的服务提供方涵盖两个服务:

  • 授权服务(Authorization Server,也叫认证服务)
  • 资源服务(Resource Server)

授权服务(Authorization Server,也叫认证服务)应包含对接入端以及用户的合法性进行验证并颁发 token 等功能。对令牌的请求端点由 Spring MVC 控制器实现,下面是配置一个认证服务必须要 实现的 endpoint:

  • AuthorizationEndpoint:服务用于认证请求。默认 URL /oauth/authorize
  • TokenEndpoint:服务于访问令牌的请求。默认 URL /oauth/token

资源服务(Resource Server):应包含对资源的保护功能,对非法请求进行拦截,对请求中 token 进行解析鉴权等,下面的过滤器用于实现 OAuth 2.0 资源服务

  • OAuth2AuthenticationProcessingFilter 用来对请求给出的身份令牌解析鉴权。

认证流程

  1. 客户端请求授权服务进行认证
  2. 认证通过后由授权服务颁发令牌
  3. 客户端携带令牌 token 请求资源服务
  4. 资源服务器校验令牌的合法性,合法即返回资源信息。

5.Spring Security OAuth2.0 实现分布式系统认证和授权示例源码

Spring Cloud Alibaba 集成 Spring Security OAuth2.0源码

参考文献

黑马程序员
OAuth2.0官方文档

你可能感兴趣的:(后端,spring,java,后端,security,oauth2.0,cloud,alibaba)