2. 鉴权与授权基础:认证(Authentication)与授权(Authorization)的区别、OAuth2.0流程、JWT令牌结构
在构建多租户SaaS系统时,鉴权与授权是安全架构的基石。本章将深入剖析认证与授权的本质区别、OAuth2.0授权码流程的完整链路,以及JWT令牌的结构与验证机制。理解这些基础概念,是后续实现鉴权拦截链路异常捕获的前提。
2.1 认证(Authentication)与授权(Authorization)的区别
认证与授权是安全领域中两个极易混淆但职责完全不同的概念。认证解决“你是谁”的问题,授权解决“你能做什么”的问题。下表从多个维度进行对比:
| 维度 | 认证(Authentication) | 授权(Authorization) |
|---|---|---|
| 核心问题 | 你是谁? | 你能做什么? |
| 执行顺序 | 先执行,是授权的前提 | 后执行,依赖认证结果 |
| 传递信息 | 用户凭证(密码、令牌、生物特征) | 权限范围(角色、资源、操作) |
| 常见协议/机制 | Basic Auth、Session-Cookie、JWT、OAuth2.0(获取令牌阶段) | RBAC、ABAC、OAuth2.0 Scopes、ACL |
| 失败结果 | 401 Unauthorized(实际应为未认证) | 403 Forbidden |
| SaaS多租户场景 | 验证用户身份,并确定其所属租户 | 验证用户对特定租户资源的操作权限 |
常见误区:HTTP 401状态码在语义上应表示“未认证”,而非“未授权”。许多开发者误将权限不足返回401,正确的做法是返回403 Forbidden。在SaaS系统中,区分这两个状态码对异常链路排查至关重要。
2.2 OAuth2.0 授权码流程
OAuth2.0是目前业界最广泛使用的授权框架,它允许第三方应用获取对用户资源的有限访问权限,而无需暴露用户密码。在多租户SaaS架构中,OAuth2.0常用于实现跨租户的API访问委托。以下是最安全的授权码模式(Authorization Code Flow)完整流程:
- 用户访问第三方应用:用户通过浏览器访问客户端应用,触发登录/授权请求。
- 客户端发起授权请求:客户端将用户重定向到授权服务器(Authorization Server),携带
client_id、redirect_uri、response_type=code及scope参数。 - 用户认证并授权:用户在授权服务器上登录,并确认授权范围(如:读取用户信息、写入文件等)。
- 授权服务器返回授权码:授权服务器通过重定向将授权码(Authorization Code)发送到客户端指定的
redirect_uri。 - 客户端换取令牌:客户端使用授权码、
client_id、client_secret在后端直接向授权服务器请求访问令牌(Access Token)和刷新令牌(Refresh Token)。 - 授权服务器返回令牌:授权服务器验证授权码和客户端凭证后,返回
access_token、refresh_token及expires_in。 - 客户端访问资源服务器:客户端使用
access_token在请求头中携带Authorization: Bearer <token>访问资源服务器(Resource Server)的受保护API。 - 资源服务器验证令牌:资源服务器验证令牌的有效性(签名、过期时间、scope),返回资源数据。
SaaS多租户关键点:在OAuth2.0流程中,授权服务器需要识别用户所属的租户,并在生成的令牌中嵌入租户ID(
tenant_id)和角色信息。资源服务器在验证令牌时,必须提取租户ID并执行租户隔离策略。
2.3 JWT 令牌结构
JSON Web Token(JWT)是一种紧凑的、URL安全的令牌格式,常用于在OAuth2.0流程中传递访问令牌。JWT由三部分组成,使用Base64URL编码,并通过数字签名保证完整性。其结构如下:
| 部分 | 名称 | 内容 | 示例 |
|---|---|---|---|
| Header | 头部 | 令牌类型(typ)和签名算法(alg),如 {"alg":"RS256","typ":"JWT"} |
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 |
| Payload | 载荷 | 声明(Claims),包括标准声明(iss、sub、exp、iat)和自定义声明(tenant_id、role) | eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidGVuYW50X2lkIjoidGVuYW50LTAwMSJ9 |
| Signature | 签名 | 对Header和Payload的签名,防止篡改。使用Header中指定的算法和密钥生成 | Ew5Q... (Base64URL编码的签名值) |
一个完整的JWT字符串格式为:Header.Payload.Signature。以下是一个解码后的JWT Payload示例,包含SaaS多租户场景下的自定义声明:
{
"sub": "user-12345",
"iss": "https://auth.saas-platform.com",
"iat": 1710000000,
"exp": 1710003600,
"tenant_id": "tenant-001",
"role": "admin",
"scope": ["read:orders", "write:orders"],
"tenant_features": ["advanced-analytics", "audit-log"]
}
安全警告:JWT的Payload是Base64URL编码,并非加密。任何拥有令牌的人都可以解码查看Payload内容。切勿在JWT中存储密码、密钥等敏感信息。如需保护内容,应使用JWE(JSON Web Encryption)进行加密。
2.4 多租户SaaS中的令牌设计要点
在多租户SaaS架构中,JWT令牌的设计需要额外考虑租户隔离和权限控制。以下是关键设计原则:
- 嵌入租户上下文:在JWT Payload中必须包含
tenant_id字段,资源服务器据此路由到对应的租户数据源。 - 角色与权限分离:使用
role字段标识用户角色(如 admin、editor、viewer),使用scope字段定义细粒度权限(如read:orders、write:invoices)。 - 令牌有效期管理:访问令牌(Access Token)有效期通常较短(15分钟-1小时),刷新令牌(Refresh Token)有效期较长(7-30天)。在SaaS中,租户管理员撤销用户权限时,需要配合令牌黑名单或短有效期实现快速失效。
- 签名算法选择:生产环境推荐使用非对称算法(RS256、ES256),授权服务器持有私钥签名,资源服务器使用公钥验证,避免密钥分发风险。
- 避免令牌膨胀:JWT会随请求头传输,过大的Payload会影响性能。只存放必要的声明,租户级别的配置信息应通过API查询,而非嵌入令牌。