王尘宇王尘宇

研究百度干SEO做推广变成一个被互联网搞的人

Spring OAuth2.0 开发指南(一):体系架构和开发概览

OAuth2.0 密码模式典型架构层次

如图所示,是密码模式的最精简架构层次,在实际开发中可以此作为基础进行扩展。密码模式涉及到五种主要角色,另外还有一个用户代理/浏览器角色:

  1. 用户代理/浏览器:一般单体应用都是前后端分离的 MVC 结构,从这个角度看,这里具体可以将用户代理/浏览器理解为前端的 H5 应用或者无线端的 APP,换句话说 H5/APP 承载用户的交互操作而成为用户代理。具体到 PAPS 演示案例就是 demo-h5;
  2. 客户端:具体指单体应用的后端服务,具体到 PAPS 演示案例就是 demo-service;
  3. 授权服务器:具体指负责认证、授权和鉴权的 IDP(Identify Provider),也是 OAuth2.0 体系的核心服务,也可以简单叫做 auth-service。具体到 PAPS 演示案例就是 idp;
  4. 受保护资源:即资源服务器,一般是内部服务,比如产品服务、订单服务等。具体到 PAPS 演示案例就是 photo-service;
  5. 资源所有者:顾名思义,即受保护资源的所有者,一般具体指代用户自己。具体到 PAPS 演示案例就是用户。

整个流程分为两个阶段:

  • 第一阶段:认证授权阶段
  1. 用户代理(demo-h5)将用户输入的用户名和密码,发送给客户端(demo-service);
  2. 客户端(demo-service)将用户输入的用户名和密码,连同 client_id + client_secret (由 idp 分配)一起发送到 idp 以请求令牌,如果 idp 约定了 scope 则还需要带上 scope 参数;
  3. idp 首先验证 client_id + client_secret 的合法性,再检查 scope 是否无误,最后验证用户名和密码是否正确,正确则生成 token。这一步也叫“认证”;
  4. idp 返回认证结果给客户端,认证通过返回 token,认证失败返回 401。如果认证成功则此步骤也叫“授权”;
  5. 客户端收到 token 后进行暂存,并创建对应的 session;
  6. 客户端颁发 cookie 给用户代理/浏览器。

至此,认证授权阶段完成。其中步骤 5-6 也有其他会话方案,比如 REST 型应用可能会将 token 存储在浏览器端,但 session/cookie 方案无疑是最稳妥的选择。

在一般的 Web 应用中,可以将此阶段看作是一次用户登录过程。

  • 第二阶段:授权后请求资源阶段
  1. 用户通过用户代理(demo-h5)访“我的相册”页面,用户代理携带 cookie 向客户端(demo—service)发起请求;
  2. 客户端通过 session 找到对应的 token,携带此 token 向资源服务器(photo-service)发起请求;
  3. 资源服务器(photo-service)向 idp 请求验证 token 有效性;
  4. idp 校验 token 有效性,再根据 scope 判断客户端(demo-service)是否有权限调用此 API,最后返回校验结果给资源服务器。这一步也叫“鉴权”;
  5. 资源服务器根据 idp 检验结果(true/false 或其他等效手段)决定是否返回用户相册数据给客户端。如果 token 校验失败则返回 401 给客户端,如果 scope 检查不通过则返回 403。这一步也叫“权限控制”。

至此,授权后请求资源阶段完成。

事实上 scope 参数不是核心的内容,实际工作中为了简化开发步骤甚至可以忽略它。scope 参数是用来约束客户端的权限的,跟用户权限(authorities)是不同的。比如可以在 idp 中利用 scope 参数约束某客户端只能发起读(GET)型请求,或只能调用指定的几个 API 等,具体业务逻辑自行编写。

密码模式的微服务架构层次和主要流程

我们仍以 PAPS 相册预览系统为例,介绍密码模式在微服务场景下的架构层次和主要流程。

OAuth2.0 密码模式微服务架构层次

微服务场景下,增加了一个网关,网关实际上是一个反向代理,将用户的请求转发到内部服务器。类似地,微服务场景下也分为两个阶段,而且第一阶段没什么变化,主要不同在于第二阶段:

  1. 用户通过用户代理(demo-h5)访问“我的相册”页面,用户代理携带 cookie 向客户端(demo—service)发起请求;
  2. 客户端通过 session 找到对应的 token,携带此 token 向网关发起对资源服务器(photo-service)的请求;
  3. 网关截取 token 连同本次请求的细节,一并向 idp 请求校验;
  4. idp 校验 token 有效性,再根据 scope 和请求细节判断客户端(demo-service)是否有权限调用此 API,最后返回校验结果给网关。如果校验全部通过,idp 生成 JWT 并返回给网关;如果 token 校验失败返回 401;如果 scope 检查不通过则返回 403;
  5. 如果校验通过,网关将得到 JWT,携带此 JWT 转发请求到资源服务器;
  6. 资源服务器解析 JWT 得到用户信息,查询用户相册数据后返回给网关;
  7. 网关将用户相册数据返回给客户端。

此流程有两项重大变化:一是加入网关使得整个流程复杂了一些;二是网关以内使用 JWT 作为令牌。关于这两点的解读,本文不再赘述,感兴趣的同学可以参阅本人早前的文章《微服务架构下的统一身份认证和授权》。

值得注意的是,步骤 9-11,还有另一种处理方法,即将 scope 客户端权限检查放到网关进行:

  1. 网关截取 token 后向 idp 请求校验;
  2. idp 校验 token 有效性,通过校验则根据 token 查询用户信息和 scope,生成 JWT 返回给网关;如果不通过则返回 401;
  3. 网关得到 JWT,解析后根据 scope 判断客户端是否有权限调用此 API,如有则携带 JWT 转发请求到资源服务器,否则返回 403 给客户端。

客户端权限检查放到网关,则网关要维护 scope 和客户端权限的逻辑。

客户端模式的微服务架构层次和主要流程

我们以 IBCS 图片分类系统为例,介绍客户端模式在微服务场景下的架构层次和主要流程。

OAuth2.0 客户端模式微服务架构层次

可以看到,客户端模式流程比较简单,这里就不再叙述具体流程了,不过请注意第 2 步:

  1. 客户端用向 idp 申请令牌之前,应该先检查是否缓存了有效令牌,有的话直接跳到第 6 步发起服务访问请求。

授权码模式的微服务架构层次和主要流程

我们仍以 PAPS 相册预览系统为例,介绍授权码模式在微服务场景下的架构层次和主要流程。

OAuth2.0 授权码模式微服务架构层次

整个流程分为两个阶段:

  • 第一阶段:认证授权阶段
  1. 用户在用户代理(demo-h5)处点击登录按钮,或请求授权登录按钮,此操作将访问客户端的某个 URI;
  2. 客户端(demo-service)将用户导向 idp 提供的认证授权页面,并在页面 ULR 参数中携带 client_id,response_type=code,redirect_uri(可选),scope(可选),state(可选);
  3. 用户通过用户代理(demo-h5),在 idp 的认证授权页面选择是否给予授权,如用户未登录,则需要先登录后再操作;
  4. 用户给予授权,idp 将用户导向 redirect_uri 指定的页面,并附加授权码(code);如果未指定 redirect_uri,则导向发起该请求时的 URI,同时附加授权码(code);
  5. 客户端收到授权码(code),向 idp 发起令牌申请,同时附上 client_id(必填) + client_secret(必填) + state(如有) + scope(如有)。注意这一步是客户端在后台发起的,用户层面无法感知;
  6. ipd 收到请求后,先核对 client_id + client_secret + scope(如有)是否无误,然后校验授权码(code),全部正确后颁发 token 给客户端。
  • 第二阶段:授权后请求资源阶段

该阶段的流程,与密码模式的微服务场景流程一致,此处不在赘述。

对于 REST 型 demo 应用

如果 demo 应用是一个 REST 型应用,则在第 1、2 步骤中,还可以这么处理:

  1. 用户在用户代理(demo-h5)处点击登录按钮或请求授权登录按钮后,通知客户端(demo-service),客户端收到通知后返回重定向的指示,以及 scope(可选),state(可选)等;
  2. demo-h5 收到响应后,直接将用户导向 idp 提供的认证授权页面,并在页面 URL 参数中携带客户端返回的参数(除了 state 参数外,其他参数可以在 demo-h5 中写死)client_id,response_type=code,redirect_uri(必选),scope(可选),state(可选),其中 redirect_uri 建议是必选的,而且必须是客户端提供的 URI(回调地址)。

还可以这么处理:

  1. 用户在用户代理(demo-h5)处点击登录按钮或请求授权登录按钮后,直接将用户导向 idp 提供的认证授权页面,并在页面 URL 参数中携带 client_id,response_type=code,redirect_uri(必选),scope(可选),但是不需要携带 state 参数,因为客户端不知道此参数的存在,其中 redirect_uri 建议是必选的,而且必须是客户端提供的 URI(回调地址)。

至此,授权码模式的认证授权全流程完毕。

讨论:客户端第一次将用户导向 idp 提供的认证授权页面时,idp 是否需要验证客户端的身份呢?或者说需不需要提供 client_secret 呢?

微服务网关的负载问题

在微服务场景下,大量的请求和响应都经过网关转发,我们设想一下,如果类似 PAPS 相册系统那样,返回的是图片数据,而且不采用 CDN 分发网络或文件存储服务等技术,那么图片流通过网关再返回给用户,网关的负载是不是将会非常庞大呢?

当然,网关本身可以做负载均衡,可以引入缓存,数据流可以做 CDN 处理等,这些都是非常好的高性能方案,除此之外,有没有其他办法呢?

这里引入一个网络技术领域的概念——负载均衡有三种模式:反向代理、透传模式和三角模式。这里简单介绍一下三角模式:

假设一种网络结构,包括 Web 服务器、PC 客户端和负载均衡器。PC 通过负载均衡器发起对 Web 服务器的访问。在三角模式下的访问流程如下:

  1. PC 向负载均衡器发起对 Web 服务器的访问请求;
  2. 负载均衡器将 PC 的请求转发给 Web 服务器;
  3. Web 服务器收到请求后,直接将响应数据发送给 PC 端。

PC、Web 服务器和负载均衡器三者呈三角形状,因此叫做三角模式。这个模式的优点就是负载均衡器只负责转发请求,而响应数据包则不需要接收和转发,从而大副降低负载均衡器自身的数据流通压力。

按照这个思路,我们可以将三角模式引入网关,从而大副降低网关的负载压力。

令牌的复用问题

我们设想一个场景,团队研发的平台同时包含了 IBCS 图片分类服务和 PAPS 相册预览服务,那么用户在登录平台(用密码模式认证授权)后,先访问“我的相册”,然后从中选择一张照片发起物品识别的请求。

根据文章的模式选型分析,IBCS 服务应采用客户端模式,PAPS 服务应采用密码模式,那么,客户端是否应该申请两套令牌?

回答这个问题,我们还是要从具体场景切入分析:

  1. 如果用户需要登录平台后才能使用 IBCS 和 PAPS 的服务,那么,只需要用密码模式一种令牌即可;
  2. 如果 PAPS 功能无需登录,游客也能使用,那么密码模式和客户端模式要分开处理。

授权码模式是最严格的,密码模式次之,客户端模式最差,因此一般情况下,授权码模式的令牌可以给其他模式使用,密码模式令牌可以给客户端模式使用,客户端模式只能自己使用。

三、Spring 家族 OAuth2 相关组件概览

好了,从本节开始我们脱离枯燥的理论环节,进入一样枯燥的实战开发频道。

目前构建 OAuth2.0 授权系统有三种方式:一是基于主流的开源组件构建;二是接入第三方授权服务(如 Google、GitHub OAuth2.0);三是根据 OAuth2.0 的标准规范自行开发相关授权组件。

常用的开源组件有 RedHat Keycloak、Spring Security、Spring Security OAuth2,以及刚起步的 Spring Authorization Server 等。值得一提的是 RedHat Keycloak,它是一款开源、成熟的 IAM 解决方案,功能强大且可私有化部署。

在 Spring 的开发生态里,建议采用 Spring Security 方向的开源组件方案,可扩展性好,定制性强,用户广泛且社区活跃。

Spring Security OAuth2 目前已停止更新,官方不推荐继续使用。相关功能已经迁移到 Spring Security,但授权服务器(Authorization Server)功能并未包含。Authorization Server 功能将由 Spring Security 团队主导开发的 Spring Authorization Server 开源组件提供,详细信息请查看官方通告:https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server

一)基于 Spring Framework

  • spring-security

核心组件,当前几乎所有 Spring OAuth2.0 开源技术方案都依赖于它。核心模块: spring-security-core。

<dependencies>
    <!-- ... other dependency elements ... -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>5.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>5.5.1</version>
    </dependency>
</dependencies>
  • [已停止更新] spring-security-oauth2

依赖于 spring-security。该组件现已合并到 spring-security 中,官方已不建议使用。在此之前是 Spring Security 团队官方维护的唯一且被广泛使用的组件。

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.5.1.RELEASE</version>
</dependency>
  • spring-security-oauth2-authorization-server

依赖于 spring-security。在 Spring Security 官方宣布停止 spring-security-oauth2 组件更新后,推出的授权服务器 Authorization Server 的替代组件。 spring-security 整合 spring-security-oauth2 后,并不包括 Authorization Server 功能,因此如果需要开发此功能,则要搭配 spring-security-oauth2-authorization-server 一起使用。

<!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server -->
<dependency>
    <groupId>org.springframework.security.experimental</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>0.1.2</version>
</dependency>

二)基于 Spring Boot

  • spring-boot-starter-security

依赖于 spring-security。相当于 spring-boot + spring-security ,默认包含 SecurityAutoConfiguration.class ,因此会执行一些自动化配置,可以简化开发步骤。如果想关闭自动配置,可以修改 Spring Boot 启动注解为 @SpringBootApplication(exclude = { SecurityAutoConfiguration.class })

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

三)基于 Spring Cloud

  • spring-cloud-starter-oauth2

依赖于 spring-boot-starter-security + spring-security-oauth2,并额外提供了许多功能实现,在微服务场景下比较实用。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
    
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

三、核心组件选型

最佳方案: spring-boot-starter-security + spring-authorization-server

由于 spring-security-oauth2 已经迁移到 spring-security,而 spring-boot-starter-security 集成了 spring-security,且做了许多简化配置,特别适合于构建 spring-boot 程序。截至 2021年8月,spring-authorization-server 的最新版本是 0.1.2,最新的消息请关注官方动态 https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server -->
<dependency>
    <groupId>org.springframework.security.experimental</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>0.1.2</version>
</dependency>

备选方案: spring-security-oauth2 或 spring-cloud-starter-oauth2

此方案采用了 spring-security-oauth2,已被 Sprnig Security 官方停止更新,因此不再推荐。另外请注意的 spring-security-oauth2 2.4.0.RELEASE 及之后的版本会提示 deprecated。

  • 如果采用 spring-security-oauth2,则 pom 引用如下:
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.8.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

<!-- spring-security-oauth2-autoconfigure 2.1.3.RELEASE 及之后的版本会提示 deprecated。
  • 或采用 spring-cloud-starter-oauth2,则 pom 引用如下:

spring-cloud-starter-oauth2 集成了 spring-security-oauth2,且做了许多简化配置,是在很长一段时间里基于开源软件构建 OAuth2 的主要方案之一。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

相关文章

评论列表

发表评论:
验证码

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。