认证与鉴权选择指南:JWT、OAuth 2.0、RBAC、ABAC、Keycloak 各自该出现的地方

56 阅读 3946 字 · 约 14 分钟

一个服务刚上线,没用户,没登录。调用方都是内部系统,相互信任——IP 白名单就够了。

后来用户来了。要区分谁是谁。再后来有管理员、有普通用户、有只读用户。角色一多,权限判断就散在代码里——if user.role == 'admin' 写了二十遍,改一次要改二十个文件。

这时候你才开始看认证鉴权方案。不是看谁文档最全,是看 你的场景卡在哪一层


认证和鉴权,先分清

认证(Authentication)回答「你是谁」——登录、验证身份、确认对面是本人。鉴权(Authorization)回答「你能做什么」——查权限、判角色、决定这个请求能不能过。

先认证身份,再查权限。认证在前,鉴权在后。两件事混在一起,改一个影响另一个——分开就各管各的。


认证:Session vs JWT

传统 Session

最直觉的做法:用户登录成功后,服务端生成一个 session 存 Redis 里,返回一个 cookie。下次请求带 cookie,服务端查 Redis 找对应的 session,找到就知道是谁。

客户端 → 登录 → 服务端生成 session → 存 Redis → 返回 cookie
下次请求 → 带 cookie → 服务端查 Redis → 找到 session → 通过

够简单。服务端控制一切——session 丢了立马清,权限改了立马生效。

但代价在 状态。每个请求都要查一次 Redis,Redis 挂了所有用户掉线。扩到多实例,每个实例都要访问同一个 Redis,延迟加一跳。防 CSRF 要自己加 token,防 session 劫持要设 HttpOnly、Secure。

JWT

JWT 换了个思路:不存状态,把用户信息直接编码进一个 token 里。服务端签发——签了名,改不了。客户端带着 token 来,服务端验签名,验完直接用里面的信息,不用查任何中央存储。

JWT 分三段:Header(用什么算法)、Payload(用户 ID、过期时间、角色)、Signature(前两段用密钥签名)。三段用 . 连起来,对机器是个字符串,对人是个长串。

Header.Payload.Signature

JWT 的好处是无状态。 任何实例都能验——不用查 Redis,不用查数据库。扩缩容跟它无关。微服务之间传身份,一个 JWT 就够了——A 服务调 B 服务,把 JWT 带上,B 自己验,不用问 A「这人是谁」。

JWT 的代价是没法主动失效。 签出去了,在过期时间之前,服务端想让它失效——做不到。token 在客户端手里,服务器没任何办法让它提前作废。要失效,只能靠黑名单——每次请求查一遍黑名单——等于又回到中心存储。JWT 把「无状态」换成了「没法主动控制」。

JWT 最典型的踩坑:把权限放 payload 里。 Payload 是 Base64 编码,不是加密——任何人拿到 token 都能解码看到里面内容。把 role=admin 放 payload,安全。把 permission=read_all_financial_data 放 payload,token 一泄露,权限全暴露。JWT 里的信息是公开的——不是加密,是签名。签名的意义是「没人能改」,不是「没人能看」。

怎么选

  • 单服务、在乎实时控制、不介意查 Redis——Session。简单,可控,要失效随时失效。单体应用天然选它。
  • 微服务、多实例、不想每次都查中心存储——JWT。无状态,服务间传身份方便。但要接受 token 没法主动失效。
  • 既要无状态、又要能失效——JWT + 短过期 + Refresh Token。JWT 设 15 分钟过期,过期了用 Refresh Token 换新的。Refresh Token 存在服务端,能随时吊销——长期状态的主动权还在服务端手里。

鉴权:从代码到模型

身份确认了,接下来是权限。权限有四种模型,不是谁比谁好,是看你的关系复杂到什么程度。

RBAC:最直接的

RBAC 是 Role-Based Access Control——角色 + 权限。用户有角色,角色有权限,权限控制操作。中间加了一层角色,用户和权限就不直接耦合了。

用户 → 角色 → 权限 → 资源

三个角色(管理员、编辑、只读)管几百个用户——改权限只改角色,不用一个个用户改。大部分系统用 RBAC 就够了。

但 RBAC 的问题是:到不了资源级。 「编辑能改文章」——改哪篇?所有篇。「管理员能看数据」——看哪个表的?所有表。RBAC 是资源类型级的——能改文章,不能改某篇特定的文章。一旦需要「这个用户只能看他所在部门的订单」,RBAC 就装不下——要加属性。

ABAC:把属性加进去

ABAC 是 Attribute-Based Access Control——不只看角色,还看属性。

用户(部门=销售) + 资源(订单) + 操作(读) + 环境(内网)→ 允许

不是「销售能看订单」,是「销售能看他自己部门的订单」。属性把权限从类型级缩小到实例级。

但 ABAC 的代价是复杂度。 属性多了,规则就多——用户的部门、资源的所有者、操作的时间、环境的 IP、当前的时间段。五六个维度交叉,一条规则写错,要么拦不住,要么全拦住。而且规则多了,排查困难——一个用户被拒了,不知道是哪条规则拒绝的。RBAC 的审计是「这个用户是什么角色」,ABAC 的审计是「这二十条规则,哪条生效了,为什么」。

ACL:最细,但最重

ACL 是 Access Control List——每条资源直接挂权限列表。

文件 A:用户 1 可读,用户 2 可写,用户 3 不可见
文件 B:用户 1 可写,用户 2 不可见

ACL 的粒度是资源级的,但管理成本是用户级。 一百万个文件,每个文件都要挂权限——改一个人,要翻遍所有文件。RBAC 的用户变了角色,角色自动生效;ACL 的用户变了权限,要一个个资源改。

ACL 适合资源少、用户少、但粒度要精确的场景——文件系统、云存储桶策略。不适合用户多、资源多的场景——管理成本随资源数线性增长。

怎么选

  • 角色多、但权限不精细到具体资源——RBAC。简单,够用,大部分系统的答案。
  • 权限要精细到具体资源、具体场景——ABAC。但要接受规则复杂度——规则多了,排查和审计都难。上 ABAC 之前先问:你真的需要这么细吗?还是 RBAC 加几个 if 就能解决?
  • 资源少、用户少、但每个资源的权限都不同——ACL。云存储、文件系统、K8s RBAC 背后实际是 ACL。
  • 刚开始、不知道以后会不会复杂——先 RBAC。复杂了再往上加属性。不要一上来就 ABAC。

OAuth 2.0:不是认证,是授权

一个常见误解:OAuth 2.0 是登录。其实不是。

OAuth 2.0 是 委托授权——允许第三方应用代表你去访问你的资源,但资源在你手里,不在第三方手里。

「用 Google 登录」:Google 告诉这个网站我是谁 → 这是 OIDC
「允许这个 App 读我的邮件」:我授权这个 App 去 Google 拿我的数据 → 这是 OAuth 2.0

OIDC(OpenID Connect)才是身份认证——建在 OAuth 2.0 上面的一个薄层。OAuth 2.0 负责授权,OIDC 负责认证——OIDC 在 OAuth 返回的 token 里多加了一个 ID Token,告诉调用方「这个人是谁」。

OAuth 2.0 的四种模式

OAuth 2.0 别当「一种协议」记,当「四种模式」——每种对应不同场景。

  • Authorization Code(授权码模式):用户→浏览器→授权服务器→返回授权码→后端拿授权码换 token。服务器对服务器,你的后端和 Google 的后端在通信。最安全,因为 token 不经过浏览器。
  • Client Credentials(客户端凭证模式):服务对服务,没有用户——后端直接拿 client_id + client_secret 换 token。微服务之间的认证。不是登录,是「这个服务确认自己是自己」。
  • Implicit(隐式模式):已废弃。token 直接返回给浏览器——太暴露了,URL 和 referrer 都可能泄露。
  • Password(密码模式):也被废弃。用户名密码直接交给第三方——第三方完全可以记录你的密码。

选 OAuth 模式,不是选哪个好——是选「用户在场还是不在场」,「浏览器参与还是不参与」。 有用户、有浏览器——Authorization Code。没用户、只有服务间——Client Credentials。都不要用密码模式——那是把自己密码交给别人,信任太强了。


怎么选,整体看

回过头看,不是比谁的协议更完善,是搞清楚你现在的需求卡在哪一层。

先分清:你在选认证还是鉴权。 认证是「谁在敲门」,鉴权是「门开了之后你能去哪」。两个问题,两套方案。

  • 单体应用、用户不多、内部系统——Session + RBAC。简单,好排查,改权限立马生效。
  • 微服务、跨服务调用、不想每个请求都查中心——JWT + OAuth 2.0 Client Credentials。服务间传 JWT,服务自己验。
  • 对外开放、让第三方访问用户数据——OAuth 2.0 Authorization Code。不要自己造轮子,用 Keycloak、Casdoor、Authentik。OAuth 是趟过的路,坑都在协议里修过了。
  • 权限精细到具体资源、具体场景——ABAC。但要接受规则复杂度的代价——规则多了排查难。大部分场景 RBAC 加几个 if 就够。
  • 已经在云上、不想自己搭认证服务器——云厂商的 IAM(AWS IAM、阿里云 RAM)。云厂商替你管用户、管权限,你自己只管策略。
  • 开源、自建、想控制一切——Keycloak。OIDC + OAuth 2.0 + SAML 全支持,自带 UI,用户管理不用自己写。缺点是要自己运维——多部署一个服务,就多一个要监控、备份、升级的东西。

最后一条:不要在代码里散落权限判断。 if user.role == 'admin' 写一次没事,写二十次就是安全漏洞——漏改一个,要么权限不够,要么权限太宽。权限判断收敛到一处(中间件、网关、或策略引擎),改了全部生效。代码里散着的权限判断,不是逻辑,是负债。