方案概览
大多数系统都是面向用户的,因此身份认证和权限管理是系统设计的重要组成部分。本篇我们对常见的认证授权方案做简要介绍,后续文章在对具体的技术方案进行介绍时会详细展开。
身份认证
当用户访问系统时,首先要解决的就是你是谁的问题,也就是身份认证。从认证方式来看主要有三种:
用户名密码:这应该是最普遍的方式,用户通常需要先注册一个账号并设置密码,并用于后续的登录。
密钥对证书:现代分布式微服务架构下,系统的不同组件也需要通信,服务之间的通信也需要保证安全性以及进行身份认证,此时用户名密码的认证方式就不再适用。此时可以通过数字证书的方式进行身份认证,比如 K8s、Istio 中使用的 mTLS 机制。MegaEase 的全栈监控系统就使用了密钥对证书的方式,通过对用户颁发自签名的 TLS 证书,用户环境上报指标时,服务端会校验其客户端证书,用来校验身份的正确性和识别是哪个租户的指标数据。
WebAuthn:这是由 FIDO 联盟领导制定的认证协议,它不在使用传统的用户名密码校验,,而是使用生物识别(指纹、人脸、虹膜、声纹)或者实体密钥(以 USB、蓝牙、NFC 连接的物理密钥容器)来作为身份凭证。
另外从认证发生的场景来看,也可以分为三类(分类来源:凤凰架构):
在通信信道进行认证:在通信建立连接之前,先证明你是谁。典型场景就是 TLS/SSL 协议认证,在通信开始前需要校验对方的证书是否可信。
在通信协议上认证:在请求资源之前,先证明你是谁。比如基于 HTTP 协议的各种认证方式。
在通信内容上认证:在使用服务之前,先证明你是谁。典型的就是我们的 Web 认证,这应该是我们日常使用最多的认证方式。作为程序员的我们通常需要设计一个认证服务来解决这个问题。
密码处理
密码的存储
在使用用户名密码进行身份认证时,服务端通常要存储用户名的密码,为了防止密码泄露,应该尽量避免明文存储,而是在存储前对密码进行处理。常见的处理方式有三种:
编码(Encoding):对密码进行编码后存储,比如 Base64 编码。但这类编码方式是可以被逆向还原的,因此不推荐使用。
加密(Encryption):对密码使用密钥进行加密,只要密钥不泄露,密码就是安全的。
哈希(Hashing):对密码进行哈希运算。哈希运算是一种单向运算,无法通过哈希值反推出原始密码,密码的微小变化都会导致哈希值的巨大变化,因此哈希运算是一种非常安全的密码处理方式。因此在实际使用中,通常都会使用哈希的方式来存储密码。
当然,哈希运算也会遇到一些攻击问题,比如暴力破解,彩虹表攻击等,为此在实际实现中,通常使用更高级的哈希算法,比如 bcrypt 算法,并引入盐值(Salt)来增加密码的安全性。
多因认证(MFA)
在用户名密码认证的基础上,为了进一步提高认证的安全性,我们可以引入 2FA(Two-Factor Authentication)或 MFA(Multi-Factor Authentication),即双因认证或者多因认证。在实际使用中,通常是用户名密码 + OTP(One-Time Password,一次性密码)的方式,OTP 可以有多种实现方式,比如:
- 短信/邮件验证码,这是我们最常见的 OTP 方式了。
- 设备消息推送,比如 Apple 设备的推送通知。
- 专门的 OTP 硬件
- 专门的 OTP 应用,比如 Google Authenticator。
单点登录(SSO)
单点登录也是认证过程的一个常见场景,其旨在解决用户在多个系统或应用之间频繁登录的问题,下面是一些常见的方案:
Kerberos:MIT设计的SSO协议,基于对称密码学,并需要一个值得信赖的第三方。其广泛用于操作系统认证,如被Windows 2000和后续的操作系统作为默认的认证方法。
OpenID Connect:基于 OAuth 2.0 + JWT 的认证协议,提供了更丰富的用户信息,功能非常的全面,是目前很流行的 SSO 方案。
CAS (Central Authentication Service):该协议由耶鲁大学设计,现在由 Apereo 社区维护,通常用于 Web 服务的单点登录,实现相对简单易用,可以用在简单的场景。
SAML (Security Assertion Markup Language):基于 XML 在身份提供者 (IdP) 和服务提供者 (SP) 之间传递认证信息,可以使用众多的场景,但协议较为复杂。
授权管理
识别了用户身份之后,第二步要解决的就是你能做什么的问题,也就是授权管理。主流的授权管理方式下面几种:
ACL(Access Control List):访问控制列表,通过列表的方式定义哪些用户可以访问哪些资源。在操作系统内部的文件系统中使用的比较广泛,比如 Linux 的文件系统,可以提供比传统的 ugo 方式更细粒度的权限控制。
RBAC(Role-Based Access Control):基于角色的访问控制,通过抽象出 Role(角色)的概念,将权限赋予不同的角色,然后将角色与用户绑定,实现灵活的授权管理。像 Kubernetes 提供了 Role/ClusterRole、RoleBinding/ClusterRoleBinding 对象支持标准的 RBAC 授权管理; Spring Security 也提供了基于角色的授权管理。通常 RBAC 可以满足大部分的授权管理需求。
ABAC(Attribute-Based Access Control):基于属性的访问控制,通过引入 Attribute(属性)的概念进行了更细粒度的授权管理。不过实现起来通常需要定义大量的属性,通常比 RBAC 更复杂。具体的实现标准是 XACML,不同语言会有相应的类库实现。
上面几种方案基本都是在单个系统中进行授权管理的,如果要涉及第三方系统的访问,比如我们的服务需要用户授权从某网盘同步数据,这时候总不能让用户把账号密码交给我们,这会造成用户的极大不便。相反,我们可以获取一个可过期的、有权限限制的 token(访问令牌),我们可以拿着这个令牌去访问用户的数据,这样就避免了用户密码的泄露。
解决第三方授权的方案主要有:
OAuth 1.0:用来进行第三方委托首选的早期协议,目前已经被 OAuth 2.0 取代。
OAuth 2.0:目前解决应用系统向第三方授权的主流协议,提供了授权码、简化模式、密码模式、客户端模式等多种授权方式,后续我们会详细介绍。
凭据管理
用户在登录认证成功后,服务端通常会生成一个凭据(Credential)返回给客户端,客户端在后续的请求中,将凭据放到 Authorization
请求头中,服务端通过该凭据来识别用户身份。主流的凭据管理有 Cookie-Session 机制和 JWT 证书两种。
Cookie-Session 机制
基于 HTTP 协议的 Cookie-Session 机制应该是最常见的凭据管理方式了。HTTP 本身是无状态的,为了维护用户的认证信息,维持客户端与服务端之间的会话状态,RFC 6265 定义了 Cookie 机制,允许服务端以键值对的方式向客户端发送信息,包括用户 ID 过期时间等信息,下面是一个示例:
Set-Cookie: user_id=12345; Expires=Wed, 30 Mar 2025 12:00:00 GMT; Path=/; HttpOnly
传统的 Cookie 机制会将 HTTP 请求头中携带一定的信息,并在客户端存储一段时间,这会导致安全隐患和不必要的传输开销。为此更常见的做法是将用户信息存储在服务端,然后生成一个 Session ID返回给客户端,客户端在后续的请求中,将 Session ID 放到 Cookie 中,服务端通过该 Session ID 来识别用户身份。
# 登录请求的响应头
Set-Cookie: JSESSIONID=3AE8CDC301F3D6C65B11BC08E065B7F8; Path=/; HttpOnly
# 后续请求的请求头
cookie: JSESSIONID=3AE8CDC301F3D6C65B11BC08E065B7F8
实际使用中通常会使用 Redis 作为缓存来存储 Session 信息,并使用 Redis 的过期时间来控制 Session 的过期时间。
JWT 证书
RFC 7519 定义了 JWT(JSON Web Token),使用 BASE64 编码后再进行签名,最后生成 JWT Token 返回给客户端。和 Cookie-Session 机制相比,JWT 将用户信息存储在 Token 也就是客户端中,JWT 非常适合在分布式系统中作为一次性令牌使用,这样可以避免 Session 机制在分布式系统下的 CAP 问题。
关于两者我们会在后续的文章中详细介绍。
云原生下的认证与授权
在云原生架构下,服务网格(Service Mesh)作为基础设施层,通常会将业务无关的功能下沉到网格中,比如认证、授权、限流、熔断、监控等,这样可以让业务专注于业务逻辑的开发。
服务网格更多的是关注服务到服务之间的安全通信,比如 Istio 提供的对等身份认证和服务授权访问,可以
- 基于 mTLS 的服务间通信的加密和认证,防止中间人攻击
- 基于服务、请求属性等要素,控制服务之间的授权访问
在认证授权方面,用户身份和权限管理依然属于应用业务逻辑的一部分,需要微服务自身来实现,但后续的校验流程可以交给服务网格实现,比如通过使用 JWT 作为令牌时,可以将公钥放置在 SideCar 中,请求在进入应用之前在 SideCar 就可以实现,Istio 就采用了这种方式,流程如下图所示。对于授权,则可以通过 SideCar 与授权服务器交互的方式完成权限校验,但通常情况下,细粒度的权限校验通常还是交由服务来实现。
总结
上述只列举了笔者在工作学习中使用或者了解过的安全认证相关的技术,由于涉猎范围有限,后续只能对常用到的一些方案做更详细的介绍和相关实践。
有一点需要注意的是,虽然技术总是不断发展,但新技术并不完全是旧技术的替代,它们只是适用场景不同,在设计时有不同的 trade-off,作为技术人员,我们需要做的就是要了解技术发展的背景、trade-off 以及局限性,然后在项目设计时做出最合适的技术选型。