从零开始的RPC(十一):统一认证与授权
理论部分
微服务安全的挑战和现状
在单体应用中,开发者可以通过简单的拦截器以及Session机制对用户的访间进行控制和记录。在目前微服务盛行的架构体系下,服务的数量在业务分解后急剧上升,每个微服务都需要对用户的行为进行认证和许可,明确当前访问用户的身份与权限级别。与此同时,整个系统可能还需对外提供一定的服务,比如第三方登录授权等。在这种情况下,如果要求每个微服务都实现各自的用户信息管理系统,既增加了开发的工作量,出错的概率也会增加。对此而言,统一的认证与授权就显得尤为必要和有效
目前主流的统一认证和授权方式有OAuth2、分布式Session和JWT等,其中又以OAuth2方案使用最为广泛,已经成为当前授权的行业标准
由于统一认证与授权方案将用户信息进行统一的管理和使用,这就很可能出现系统性能瓶颈的问题,甚至可能在认证和授权服务宕机会导致整个系统无法正常运行。与此同时,整合当前系统中各个服务的用户信息管理系统也存在一定的难度,在实践统一认证与授权方案时需要根据项目的现状理智选择方案
常见的认证与授权方案
行业授权标准 OAuth2.1
OAuth 2.1 是 OAuth 2.0 的安全增强版本,旨在解决 OAuth 2.0 中的安全问题并简化规范。它于 2021 年发布,是目前最新的行业授权标准。
OAuth 2.1 的核心角色
OAuth 2.1 定义了四个核心角色:
资源所有者(Resource Owner)
- 能够授予对受保护资源访问权限的实体
- 通常是最终用户(如登录的用户)
资源服务器(Resource Server)
- 托管受保护资源的服务器
- 能够根据访问令牌验证请求并提供资源
客户端(Client)
- 代表资源所有者请求访问受保护资源的应用程序
- 可以是网站、移动应用、桌面应用等
授权服务器(Authorization Server)
- 负责验证资源所有者身份并颁发访问令牌
- 管理授权过程和令牌生命周期
OAuth 2.1 协议流程
OAuth 2.1 的基本流程如下:
- 客户端请求授权:客户端向授权服务器请求授权
- 用户认证:资源所有者(用户)进行身份认证
- 用户授权:用户授权客户端访问其资源
- 颁发授权码:授权服务器向客户端颁发授权码
- 交换访问令牌:客户端使用授权码向授权服务器交换访问令牌
- 访问资源:客户端使用访问令牌向资源服务器请求受保护资源
- 验证令牌:资源服务器验证访问令牌的有效性
- 提供资源:验证通过后,资源服务器向客户端提供受保护资源
+--------+ +---------------+
| |--(A)- 请求授权 ------------------------->| |
| | | |
| |<-(B)- 认证用户 ------------------------- | |
| | | 授权服务器 |
| |<-(C)- 授权用户 ------------------------- | |
| 客户端 | | |
| |--(D)- 交换授权码获取访问令牌 ----------->| |
| | | |
| |<-(E)- 颁发访问令牌 ----------------------| |
+--------+ +---------------+
|
|
v
+-------------------------+
| |
|--(F)- 使用访问令牌请求资源 ->| 资源服务器
| |
|<-(G)- 验证令牌并提供资源 ----| |
+-------------------------+OAuth 2.1 客户端授权类型
OAuth 2.1 支持以下授权类型:
1. 授权码流程(Authorization Code Flow)
适用场景:Web 应用、移动应用、桌面应用等所有类型的客户端
特点:
- 最安全的授权方式
- 支持 PKCE(Proof Key for Code Exchange)增强安全性
- 适用于有后端的应用
流程:
- 客户端引导用户到授权服务器
- 用户登录并授权
- 授权服务器返回授权码
- 客户端使用授权码和客户端密钥交换访问令牌
2. 隐式授权流程(Implicit Flow)
注意:OAuth 2.1 已废弃此流程,建议使用授权码流程 + PKCE
3. 客户端凭证流程(Client Credentials Flow)
适用场景:服务器间通信、后台服务
特点:
- 不需要用户参与
- 直接使用客户端 ID 和密钥获取令牌
- 适用于机器间的认证
流程:
- 客户端使用客户端 ID 和密钥直接向授权服务器请求
- 授权服务器验证并返回访问令牌
4. 密码凭证流程(Password Flow)
注意:OAuth 2.1 已废弃此流程,建议使用授权码流程
5. 设备授权流程(Device Authorization Flow)
适用场景:无浏览器或输入受限的设备(如智能电视、智能家居设备)
流程:
- 设备显示用户代码和验证 URL
- 用户在其他设备上访问 URL 并输入代码
- 用户登录并授权
- 设备轮询授权服务器获取访问令牌
OAuth 2.1 的安全增强
OAuth 2.1 相比 OAuth 2.0 的主要安全增强:
- 强制使用 PKCE:所有授权码流程必须使用 PKCE,防止授权码拦截攻击
- 废弃不安全流程:移除了隐式授权和密码凭证流程
- 简化刷新令牌:统一了刷新令牌的处理方式
- 增强令牌安全:推荐使用短期访问令牌和刷新令牌
- 改进重定向 URI 验证:更严格的重定向 URI 验证规则
令牌类型
OAuth 2.1 定义了两种主要的令牌类型:
访问令牌(Access Token)
- 用于访问受保护资源
- 通常短期有效(如 15-60 分钟)
- 可以是 JWT 或不透明令牌
刷新令牌(Refresh Token)
- 用于获取新的访问令牌
- 长期有效(如几天到几个月)
- 存储在客户端安全的位置
OAuth 2.1 的最佳实践
- 始终使用 HTTPS:保护所有 OAuth 通信
- 使用 PKCE:增强授权码流程的安全性
- 安全存储客户端密钥:不要在客户端代码中硬编码
- 设置合理的令牌过期时间:访问令牌短期,刷新令牌长期
- 实现令牌撤销机制:允许用户撤销授权
- 使用范围(Scope):限制访问权限的范围
- 验证重定向 URI:确保只接受预注册的重定向 URI
常见的 OAuth 2.1 提供商
- Google:使用 OAuth 2.0(接近 2.1 标准)
- Facebook:使用 OAuth 2.0
- GitHub:使用 OAuth 2.0
- Microsoft Azure AD:支持 OAuth 2.0 和 OpenID Connect
- Auth0:支持 OAuth 2.1 标准
- Okta:支持 OAuth 2.1 标准
数据共享:分布式 Session
在 Web 服务中,Session 和 Cookie 是维护用户登录状态的传统方式。随着分布式系统和微服务架构的普及,单体应用中的 Session 管理方式已无法满足需求,分布式 Session 管理应运而生。
会话跟踪技术:Session 与 Cookie
会话(Session) 是指用户与应用交互的一系列连续操作,通常包含多个 HTTP 请求。由于 HTTP 协议的无状态特性,服务器无法在多次请求之间保持用户状态,因此需要会话跟踪技术来解决这个问题。
Session 和 Cookie 是最常用的会话跟踪机制:
Cookie:
- 存储在客户端浏览器中,是一小段文本信息
- 由服务器在响应中设置,客户端在后续请求中自动携带
- 通常包含会话标识(Session ID),用于服务器查找对应 Session
- 可设置过期时间、路径、域等属性
Session:
- 存储在服务器端,包含用户的状态信息
- 与客户端 Cookie 中的 Session ID 一一对应
- 可存储更复杂的用户数据,安全性更高
- 通常有服务器管理的过期时间
工作流程:
- 客户端首次请求服务器
- 服务器创建 Session,并生成唯一 Session ID
- 服务器将 Session ID 放入 Cookie 并返回给客户端
- 客户端后续请求自动携带该 Cookie
- 服务器根据 Cookie 中的 Session ID 查找对应的 Session 信息
- 服务器根据 Session 信息识别用户身份和状态
分布式 Session 的挑战
在单体应用中,Session 存储在单个服务器的内存中,管理简单。但在分布式架构中,面临以下挑战:
- Session 不一致:用户请求可能被负载均衡器分发到不同服务器
- Session 丢失:服务器重启或崩溃会导致 Session 丢失
- 扩展性差:Session 存储在服务器内存中限制了水平扩展能力
典型场景:
- 用户在服务器 A 登录,Session 存储在 A 上
- 后续请求被负载均衡到服务器 B
- 服务器 B 无法找到用户的 Session,导致用户需要重新登录
分布式 Session 的实现方案
| 实现方式 | Session 复制 | Session 粘滞 | 集中式管理 | 基于客户端 Cookie 管理 |
|---|---|---|---|---|
| 原理 | 多台服务器之间同步 Session 数据 | 通过负载均衡器将用户请求固定到特定服务器 | 使用独立存储服务(如 Redis)集中管理 Session | 将 Session 数据加密后存储在客户端 Cookie 中 |
| 优点 | 对开发透明,无需代码修改 | 实现简单,无额外存储开销 | 可靠性高,支持水平扩展 | 无依赖外部服务,部署简单 |
| 缺点 | 网络开销大,同步延迟,内存占用高 | 单点故障风险,负载不均 | 依赖外部存储服务,增加系统复杂度 | 安全性低,Cookie 大小限制,性能受影响 |
| 适用场景 | 服务器数量少,Session 数据量小 | 对高可用要求不高的场景 | 大型分布式系统,高可用要求 | 数据不敏感,用户体验要求高的场景 |
| 技术实现 | Tomcat Session 复制 | Nginx IP 哈希,会话粘性 | Redis、Memcached 存储 | 加密 Cookie,JWT |
最佳实践
推荐方案:集中式管理(Redis)
- 使用 Redis 作为 Session 存储,支持高并发和高可用
- 利用 Redis 的持久化和集群特性保证数据安全
- 提供统一的 Session 管理接口,简化开发
实现建议:
- 设置合理的 Session 过期时间(如 30 分钟)
- 对敏感数据进行加密存储
- 实现 Session 防篡改机制
- 考虑使用分布式锁处理并发更新
与其他方案对比:
- 分布式 Session vs JWT:JWT 无状态,适合前后端分离;Session 有状态,适合传统 Web 应用
- 分布式 Session vs OAuth2:OAuth2 更适合第三方授权场景,Session 更适合内部系统认证
技术选型考量
- 性能要求:Redis 等内存数据库性能优异,适合高并发场景
- 可靠性要求:多副本、持久化机制保证数据安全
- 运维复杂度:需要额外管理存储服务
- 成本考虑:增加了基础设施成本和维护成本
在实际项目中,应根据业务规模、性能要求和团队技术栈选择合适的分布式 Session 方案。对于大型微服务架构,集中式管理(Redis)通常是最优选择。
安全传输对象 JWT
JWT(JSON Web Token) 作为一个开放的标准,通过紧凑(快速传输,体积小)并且自包含(有效负载中将包含用户所需的所有的信息,避免了对数据库的多次查询)的方式,定义了用于在各方之间发送的安全JSON对象
JWT可以很好地充当OAuth2的访问令牌和刷新令牌的载体,这是Web双方之间进行安全传输信息的良好方式。当只有授权服务器持有签发和验证JWT的secret时,也就只有授权服务器能验证JWT的有效性以及签发带有签名的JWT,这就唯一保证了以JWT为载体的token的有效性和安全性
JWT一般表现形式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30它由3部分组成,每部分通过.分隔开,分别是:
Header/头部
- typ:类型,其值一般为 JWT
- alg:加密算法,通常为 HMAC、SHA256或HS256

这部分会被Base64URL编码为JWT的头部:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Payload/载荷:载荷是JWT的第二部分,是用来携带有效信息的载体,主要是关于用户实体和附加元数据的声明,由以下三部分组成:
- Registered claims/注册声明:它是一组预定的声明,但并不强制要求使用。主要有iss(JWT签发者)、exp(JWT过期时间)、sub(JWT面向的用户)、aud(接受JWT的一方) 等属性信息
- Public claims/公开声明:在公开声明中可以添加任何信息,一般是用户信息或者业务扩展信息等
- Private claims/私有声明:它是被JWT提供者和消费者共同定义的声明,既不属于注册声明也不属于公开声明
一般不建议在Payload中添加任何的敏感信息,因为Base64是对称解密的,这意味着Payload中的信息的是可见的
这部分会被Base64 URL编码为JWT的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0Signature/签名
要创建签名,必须需要被编码后的头部、被编码后的有效负载以及一个secret,最后通过在头部定义的加密算法alg加密生成签名,生成签名的伪代码如下:HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret, )上述伪代码中使用的加密算法为HMACSHA256。Secret作为签发密钥,用于验证JWT以及签发JWT,所以只能由服务端持有,不该泄漏出去
实践部分
基于OAuth 2.1 + JWT 实现的简单认证与授权系统
系统整体架构
项目结构
仍然使用Go Kratos框架,并且使用Service布局——Admin布局自带权限控制服务和页面,不适于当前所实践的部分