为什么OAuth2与SSO经常混为一谈?

简介

最近在工作中遇到了一个问题,在实现OAuth2的过程中,发现公司的实际落地与理论不完全相同。故此复习一下。

What is OAuth2?

OAuth2(OAuth2.0)是一个开放标准的授权框架,用于第三方应用(客户端)在取得用户(资源所有者)的授权下,可以访问用户敏感信息。而无需向第三方应用暴露账号密码

核心是"授权"而非"认证"

  1. 设计之初聚焦于权限管理

    OAuth2诞生的初衷是为了避免第三方应用获取用户的原始密码,同时让用户(资源所有者)自主控制第三方应用(客户端)对自己敏感信息的访问权限(比如只读,修改,删除),其本质是"授权第三方有限度的使用资源"

  2. 本身不参与认证

    在OAuth2流程中,用户身份的验证始终由资源所有者的平台负责(比如微信,GITHUB),由它们提供Login页面。

    OAuth本身不参与用户身份的验证,它只是在验证通过后,传递"已授权"的凭证

  3. 仅仅是一份授权证明

    OAuth2最终发放的是"访问令牌(Access Token)",它仅仅代表用户允许第三方应用如何访问特定资源,不包含用户身份信息,也不能作为用户身份的证明。

举个例子,使用该Access Token在https://www.jwt.io/ 中解析。eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1Niwic2NvcGUiOiJwcm9maWxlIG9wZW5pZCIsImV4cCI6MTczMzcwMjQwMCwiaWF0IjoxNzMzNjk4ODAwLCJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

核心角色

OAuth2定义了4个核心角色,所有流程都围绕这4个角色展开

角色 含义 举个例子
资源所有者 用户本身,有权决定是否授权客户端访问自己的资源 使用微信的你
客户端 第三方应用,希望访问用户(也就是你)的敏感信息 比如小红书希望访问你微信上的照片,头像,昵称等
授权服务器 由资源服务器提供,验证用户的身份,生成授权凭证(Access Token) 微信的授权服务器
资源服务器 存储受保护资源的服务器,接收并验证客户端的Access Token 微信的用户信息服务器(存储你的头像、昵称等,只有验证令牌后才返回)。

核心授权类型(Grant Type)

OAuth2根据客户端的类型(是否有服务器,是否能安全存储密钥)定了多种Grant Type。

  1. 授权码模式(Authorization Code)

    • 适用场景: 有自己服务器的客户端,能够安全存储客户端密钥,不会暴露给前端
    • 特点: 通过授权码间接获取访问令牌,是最常用也是最安全的模式。
  2. 隐式模式(Implicit)

    • 适用场景: 有自己服务器的客户端(比如无需与后端交互的纯前端应用),无法安全存储客户端密钥(密钥会暴露在前端代码中)
    • 特点:跳过"授权码"步骤,直接从授权服务器获取访问令牌(通过URL片段返回),不返回刷新令牌。
    • 缺点:访问令牌直接暴露在前端,安全性较低(可能被拦截)。
  3. 密码模式(Resource Owner Password Credentials)

    • 适用场景:客户端与资源所有者高度信任(如公司内部应用),用户愿意向客户端提供账号密码。
    • 流程:用户直接向客户端输入账号密码,客户端拿着密码向授权服务器请求访问令牌(如"公司内部APP用域账号密码登录,获取员工信息系统的访问权限")。
    • 缺点:客户端获取了用户密码,违背OAuth"不暴露密码"的初衷。
  4. 客户端模式(Client Credentials)

    • 适用场景:客户端访问自己的资源(非用户个人资源),无需用户授权。
    • 流程:客户端直接用自己的客户端ID和密钥向授权服务器请求访问令牌(如"天气APP的后端服务,用自己的凭证获取天气数据API的访问权限")。

重点关注授权码模式(Authorization Code)即可,剩下的三种纯属凑数。谁敢在生产环境暴露密钥?谁又能保证密钥永远不会泄露?

核心凭证

OAuth2中有3种关键凭证,用于确保授权流程的安全性

  1. 授权码(Authorization Code)

    • 短期有效(几分钟),由授权服务器在用户同意授权后生成。
    • 仅能使用一次,兑换令牌后失效,避免被劫持后重复使用。
  2. 访问令牌(Access Token)

    • 客户端访问资源服务器的"key",包含了授权范围(比如只能访问头像,不能访问手机号)
    • 通常为JWT,资源服务器通过它验证客户端的访问权限。
  3. 刷新令牌(Refresh Token)

    • 长期有效(几天到几个月),用户在Access Token过期后,无需重新授权,直接获取Access Token。

Authorization Code 核心流程

Q1:授权服务器生成Code之后,redirect_uri重定向回前端还是后端?

授权服务器生成 Code 后,通过 redirect_uri 首先发送给第三方客户端的前端(浏览器 / APP),再由前端中转到客户端的后端,最终由后端用 Code 交换 Access Token。
Q2:为什么Code一定要重定向回前端,后端不行吗?

可以,但会造成如下几个问题:

  1. 用户授权流程的缺失
    当用户同意授权后,如果直接发给后端。那么用户的前端页面完全不知道授权结果,比如用户在微信登录页授权后,浏览器还停留在微信页面,不知道该跳转回到客户端哪里。授权流程被割裂,对于用户体验不好。
  2. 无法验证客户端的合法性
    如果直接发后端,授权服务器只能通过RedirectUri来判断,但这个地址可能会被伪造,比如攻击者伪装成客户端后端地址,诱导授权服务器把 Code 发给他。HTTPS的中间人攻击也是这个原理。
    而通过前端重定向,「用户的上下文」就是天然的验证:用户是从客户端前端跳转去授权服务器的,授权后跳回客户端前端,相当于用户 "背书" 了这个客户端的合法性,避免了攻击者伪造后端接收 Code 的风险。
  3. 网络可达性问题
    后端地址可能是企业内网,也可能是有防火墙,跳板机,堡垒机等保护。授权服务器根本无法直接访问这些后端接口
    而网页前端天生自带公网IP,授权服务器只需通过 "跳转" 把 Code 交给前端,再由前端中转给后端,完美解决了 "授权服务器找不到客户端后端" 的问题

为什么OAuth2与SSO经常混为一谈?

SSO是一种"业务目标",而OAuth2是实现该目标的常用方案之一。它们是两个不同的概念,既不冲突也不等同,而是 "目标与工具" 的关系。

OAuth2可以实现SSO,但不是SSO的唯一实现方式。还有其它协议,比如SAML2.0,CAS,以及非常轻量化的Cookie共享,Ticket自包含等方案。

它们混淆的根源在于:

  1. 场景高度重叠

    OAuth2最常见的场景就是第三方登录 ,比如使用微信登录美团,淘宝等第三方客户端。
    在用户的视角下 ,只要使用微信登录,就能登录多个应用一气呵成,很流畅很润,对于用户最直观的感受就是,这就是SSO。

    在开发者的视角下,对接第三方登录时,市面上90%的方案就是OAuth2协议,久而久之就形成了刻板印象与路径依赖。

  2. 概念边界很模糊,认证与授权往往同时出现

    主流基于OAuth2的SSO方案,不会只做"身份认证",还会同步处理"权限校验"。而OAuth2刚好能同时解决,导致SSO与OAuth2深度绑定,难以拆分理解

我就经常混淆.NET中app.UseAuthentication()与app.UseAuthorization() ,他们两个的单词拼写都很接近,更别说概念了。

两者核心差异

OAuth2的核心是授权 ,即使不用来做SSO,它也能单独解决"第三方应用访问用户资源"的问题。
SSO的核心是认证,即使不用OAuth2,它也能解决"重复登录"的问题,比如使用JWT形式的Ticket,也能实现SSO,与授权无关。

OAuth2如何勾搭上SSO的?

在文章中,一直强调OAuth2是对资源的管理,好像跟认证没啥关系啊。为什么能扯上关系?
正所谓,弹道也是道,枪法也是法,保时捷也是劫

当今互联网识别用户唯一标识的主流,分别是Email&Phone。

我们可以把这两者当作一种资源去申请,申请到之后,跟自己数据库匹配,如果存在那就直接颁发token/cookie,如果不匹配,就自动创建用户并颁发token/cookie。 从而以一种曲线救国的方式完成了免密登录。

简单DEMO

https://gitee.com/lmy5215006/auth-learning-work

埋了一个彩蛋,再次解释了为什么Code一定要重定向回前端。