几种OAuth鉴权方式的原理、设计、适用场景
鉴权方式演进关系及适用场景
在现代应用中,鉴权(Authorization)是确保安全性的关键环节。随着技术的发展,OAuth协议不断演进,形成了多种鉴权方式。以下是几种主要鉴权方式的演进关系与适用场景。
授权码授权(Authorization Code Flow)
可以将授权码授权类比为“申请签证”。用户(客户端)首先向授权服务器(签证官)申请一个“签证申请码”(授权码),然后再用这个码去申请真正的签证(访问令牌)。这种方式适用于需要安全性较高的场景,比如:
- Web应用:需要用户通过浏览器进行身份验证。
- 服务端应用:可以安全存储客户端密钥。
OAuth PKCE
PKCE(Proof Key for Code Exchange)可以看作是对传统签证申请的增强版,增加了一个“验证码”的步骤。用户在申请签证时,还需要提供一个随机生成的验证码,以防止他人冒用。这种方式特别适合:
- 移动应用:因为移动设备无法安全存储密钥。
- 单页应用(SPA):避免了中间人攻击。
设备授权模式(Device Authorization Grant)
设备授权模式就像是在公共场所使用的自助服务机。用户在设备上输入一个代码,然后在手机或电脑上确认。这种方式适合:
- 智能电视、IoT设备:这些设备通常没有键盘或鼠标,用户需要在其他设备上完成认证。
OAuth JWT 授权(开发者)
JWT(JSON Web Token)可以想象成一种“电子护照”,它包含了用户的信息和权限,可以直接用于访问资源。开发者使用这种方式时,适合的场景包括:
- API服务:需要快速验证用户身份和权限。
- 微服务架构:不同服务之间需要共享用户信息。
OAuth JWT 授权(渠道场景)
在渠道场景中,JWT就像是一个“通行证”,允许多个合作伙伴访问同一资源,但每个合作伙伴只能看到自己被授权的信息。这种方式适合:
- 多方合作项目:不同公司或团队之间需要共享数据,但又要保持数据隔离。
OAuth 授权(多人协作场景)
多人协作场景可以比作一个“团队账户”,团队中的每个人都有不同的权限,大家可以共同访问某些资源。这种方式适合:
- 项目管理工具:团队成员可以根据角色获得不同的访问权限。
- 共享文档平台:允许多人协作编辑和查看文档。
OAuth 包括以下角色
- 客户端 本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:畅购 Android客户端、畅购Web客户端(浏览器端)、微信客户端等。
- 资源拥有者 通常为用户,也可以是应用程序,即该资源的拥有者。
- 授权服务器(也称认证服务器) 用来对资源拥有的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资源拥有者授权后方可访问。
- 资源服务器 存储资源的服务器,比如,畅购用户管理服务器存储了畅购的用户信息,微信的资源服务存储了微信的用户信息等。客户端最终访问资源服务器获取资源信息。
OAuth 在项目的应用
Oauth2是一个标准的开放的授权协议,应用程序可以根据自己的要求去使用Oauth2,项目中使用Oauth2可以实现实现如下功能:
- 本系统访问第三方系统的资源
- 外部系统访问本系统的资源
- 本系统前端(客户端) 访问本系统后端微服务的资源。
- 本系统微服务之间访问资源 例如:微服务A访问微服务B的资源,B访问A的资源。
OAuth2相关概念
令牌:令牌是OAuth2中的核心组件,它代表了一次授权的结果,表示客户端已经被授予它所请求的权限 客户端:是代表资源拥有者访问受保护资源的软件。简单来说OAuth2授权给谁,谁就是OAuth2的客户端。资源拥有者:是有权将访问权限授权给客户端的主体。受保护的资源:是能够通过http服务器进行访问,访问时需要OAuth2下发的令牌(token)。授权服务器:是一个HTTP服务器,它在OAuth2系统中充当中央组件。授权服务器对资源拥有者和客户端进行身份认证,让资源拥有者向客户端授权、为客户端颁发令牌。授权范围:表示一组访问受保护资源的权限,它界定了客户端获取的权限范围。刷新令牌:与访问令牌的不同在于,该令牌不会被发送给受保护资源,它只是为了当访问令牌过期后,客户端不必重新发起授权请求,而是用该令牌向授权服务器请求换取一个新的用于获取受保护资源的访问令牌。
总结
博文到这里可能还是过于抽象,我们从实际生活中举例来更好地理解。
比如小区大门和楼栋有门禁系统,进入需要输入密码或刷卡,我是做电商的,快递员经常需要来取件,如果我把密码告诉快递员他就拥有和我一样的权限了,这样肯定是不好的。如果此时有授权机制,快递员想进来需要得到我的授权,我在家授权后,门禁系统生成一个令牌,快递员得到这个令牌三天或一周之内都可以小区和楼栋。
令牌与密码的作用是一样的,都可以进入系统,但是有三点差异。
- 令牌是短期的,到时间会自动失效,无法修改。密码一般长期有效,我不改它就一直可以用。
- 令牌可以被撤销,立即失效。以上例而言,我可以随时取消快递员的令牌。密码一般不允许被他人撤销。
- 令牌有权限范围(scope),比如只能进小区或某个楼栋。密码一般是完整权限。
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。
OAuth2 的核心概念
OAuth2 的核心概念包括:
- Redircect URI:重定向 URI 是客户端应用程序接收授权码的 URL。
- Authorization Endpoint:授权终端是用于验证用户身份并颁发授权码的服务器端点。
- Access Token:访问令牌是用于访问受保护资源的令牌。
- Refresh Token:刷新令牌是用于更新访问令牌的令牌。
- Scope:范围是指定访问令牌权限的参数。
- Client ID:客户端 ID 是用于标识客户端应用程序的唯一标识符。
- Client Secret:客户端密钥是用于验证客户端应用程序的机密字符串。
- Resource Owner:资源所有者是拥有受保护资源的用户。
- Resource Server:资源服务器是存储受保护资源的服务器。
- Authorization Server:授权服务器是用于验证用户身份并颁发访问令牌的服务器。
- Grant Type:授权类型是指定客户端应用程序如何获取访问令牌的参数。
- Token Endpoint:令牌终端是用于验证授权码并颁发访问令牌的服务器端点。
OAuth2 的授权模式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
OAuth2 的工作流程
OAuth2的工作流程如下:
- 客户端向用户请求授权。
- 用户同意授权,客户端获得授权码。
- 客户端使用授权码向授权服务器请求令牌。
- 授权服务器验证授权码,验证通过后,向客户端发放令牌。
- 客户端使用令牌向资源服务器请求资源。
- 资源服务器验证令牌,验证通过后,向客户端返回资源。
- 客户端使用资源。
Oauth2.0 授权流程
角色
用户,客户端(可以理解成前端),授权服务器(负责验证用户身份并颁发令牌的服务器),资源服务器(根据令牌向客户端提供访问权限)
过程
- 客户端请求授权:客户端向授权URL发请求,要求用户同意授权,同意后授权服务器会把客户端重定向到回调URL,并附带授权码
- 客户端获得访问令牌:使用上一步获得的授权码,以及ID和密码访问授权服务器,以获取访问令牌
- 授权服务器验证并颁发令牌:授权服务器验证上一步的参数和授权码,成功以后返回一个访问令牌给客户端
- 客户端使用令牌访问资源服务器:令牌作为客户端请求的一部分发给资源服务器
简化版:访问授权URL → 获取授权码,返回到客户端回调URL → 授权码和ID密码一起发给授权服务器 → 授权服务器返回令牌 → 客户端用令牌可以访问资源服务器
OAuth 2.0 和 OpenID Connect (OIDC) 是用于授权和认证的两个标准协议。它们通常一起使用,以提供更安全和更可靠的用户认证和授权服务。
OAuth 2.0
简介
OAuth 2.0 是一个用于授权的开放标准协议,允许第三方应用在用户授权的情况下访问用户的资源,而无需暴露用户的凭证(例如密码)。OAuth 2.0 定义了四种主要的授权类型:
授权码授权(Authorization Code Grant)
常用于服务器端应用。用户通过浏览器向授权服务器请求授权码,然后应用使用该授权码向授权服务器请求访问令牌。
隐式授权(Implicit Grant)
常用于客户端应用(如单页应用)。用户直接在浏览器中获得访问令牌,而无需通过中间的授权码。
资源所有者密码凭证授权(Resource Owner Password Credentials Grant)####
用户直接提供用户名和密码给客户端应用,客户端应用使用这些凭证向授权服务器请求访问令牌。
客户端凭证授权(Client Credentials Grant)####
用于应用之间的通信。客户端应用使用自己的凭证(而不是用户的凭证)向授权服务器请求访问令牌。
流程
- 用户向客户端应用发出请求。
- 客户端应用将用户重定向到授权服务器,请求用户授权。
- 用户在授权服务器上进行认证并同意授权。
- 授权服务器返回授权码或访问令牌。
- 客户端应用使用授权码向授权服务器请求访问令牌(如果使用授权码授权) 。
- 客户端应用使用访问令牌访问用户的资源。
OpenID Connect (OIDC)
简介
OpenID Connect 是基于 OAuth 2.0 的一个认证层。它不仅允许客户端应用获取访问令牌,还允许它们获取ID令牌(ID Token),该令牌包含了关于用户的信息(例如用户ID、用户名、电子邮件地址等)。OIDC 的目标是让应用能够安全地获取和验证用户的身份信息。
主要组件
身份提供者(Identity Provider, IdP)####
负责认证用户并签发ID令牌和访问令牌。
客户端应用(Relying Party, RP)####
向身份提供者请求ID令牌和访问令牌,以获取用户的身份信息和访问受保护的资源。
流程
- 用户向客户端应用发出请求。
- 客户端应用将用户重定向到身份提供者,请求用户认证和授权。
- 用户在身份提供者上进行认证。
- 身份提供者返回授权码给客户端应用。
- 客户端应用使用授权码向身份提供者请求访问令牌和ID令牌。
- 客户端应用使用访问令牌访问用户的资源,并使用ID令牌获取用户的身份信息。
OAuth 2.0 和 OIDC 的关系
OAuth 2.0 主要用于授权:允许第三方应用在用户授权的情况下访问用户资源。
OIDC 是基于 OAuth 2.0 的认证层:提供用户身份验证和用户信息获取的功能。
实践中的应用
在实际应用中,OAuth 2.0 和 OIDC 通常一起使用,以实现既安全又灵活的用户认证和授权。例如:
- 单点登录(SSO) :使用OIDC实现用户的单点登录,用户可以在一个应用中登录后,无需再次登录即可访问其他集成的应用。
- API 访问:使用OAuth 2.0授权第三方应用访问用户的API资源,而无需直接暴露用户的凭证。
主要优势
- 安全性:通过OAuth 2.0和OIDC,用户无需将凭证直接提供给第三方应用,减少了凭证泄露的风险。
- 用户体验:用户可以在不同的应用中使用统一的身份认证,提高了用户体验。
- 灵活性:支持多种授权类型和认证方式,适应不同的应用场景和需求。
总结
OAuth 2.0 和 OIDC 是现代应用中常用的授权和认证协议。它们的结合使得应用能够安全、可靠地进行用户认证和授权,保护用户信息的安全,同时提供良好的用户体验。
授权与认证
我们一直在反复阐述一个事实: OAuth2 协议是用来解决用户对软件授权的。为何要如此强调这个事实呢?因为授权、认证、单点登录非常容易被混淆。
用户认证是指证明自己的身份。认证有时候会和授权一起使用,就像你去酒店必须首先用身份证证明你的身份,酒店才会授予你使用酒店服务的权利一样。
OAuth2 与 OIDC
今天要介绍的是 OpenID connect 简称 OIDC 是一个关于第三方用户认证的标准化协议。这个协议是建立在我们之前介绍的 OAuth2.0 的基础上的。这又是一个容易混淆的地方:虽然授权和认证在语义上明显不同,但是他们却使用类似的协议来解决问题。还记得之前我们谈到过 OAuth2.0 是一个授权协议,但是他的功能不仅仅如此。OAuth 可以被用来解决任何问题,身份认证就是其中一种。
于是,基于 OAuth 2.0 的协议流程,第三方用户认证协议 OpenID connect 就被设计出来了。所以 OIDC 是 OAuth2.0 的超集,但是认证并不是授权的超集。
OIDC 授权码许可类型流程简介
是的,OIDC 也有两种常用类型:授权码许可类型与隐式许可类型。由于 OIDC 在 OAuth2.0 的基础上被设计出来,所以 OAuth2.0 的流程基本上就是 OIDC 的流程。
在 OIDC 的流程中,相对 OAuth2.0 有 3 个变化。
- 授权服务器 + 资源服务器 = 认证服务器。
- 根据授权码获取的 token 中包含 id_token。
- 用户详情可以使用 UserInfo 端点获取。
认证服务器的变化很容易理解,由于身份认证不需要使用特定资源只需要对身份认证,所以整合了授权服务器和资源服务器合并为认证服务器。
但是单独设计 id_token 的理由是什么呢?在授权的场景中,access_token 在协议中是对客户端透明的,不需要被解析。而身份认证需要告诉客户端用户的具体身份;另外 access_token 的用处是访问受保护资源,这是属于授权的概念而不是属于身份认证的概念;最后授权的有效时间和身份认证的有效时间很可能不一样,所以单独设计了 id_token。
id_token 是一个内容基于 jwt 格式的身份令牌(结构如下)。客户端通过解析这个令牌获取用户的登录认证数据以后,就可以为用户提供基于认证中心的第三方登录功能了。
当然偶尔这个 jwt 的有效载荷会更加充实一点:
{
"iss": "http://server.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"gender": "female",
"birthdate": "0000-10-31",
"email": "janedoe@example.com",
"picture": "http://example.com/janedoe/me.jpg"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
至于 UserInfo 端点,由于 jwt 格式的 id_token 并不包含过多的用户业务信息,而在用户体验上客户端又需要向用户展示头像、昵称等内容,所以 OIDC 定义了客户端可以使用 access_token 访问 UserInfo 端点获取用户详情的标准化接口。
HTTP/1.1 200 OK
Content-Type: application/json
{
"sub": "example@hotmail.com",
"name": "example",
"given_name": "example",
"family_name": "example",
"preferred_username": "example@hotmail.com",
"email": "example@gmail.com"
}
2
3
4
5
6
7
8
9
10
11
同时这也是 access_token 用于访问受保护资源,id_token 用于身份认证的一次结合应用。
说了这么多,那学习 OIDC 有什么用呢? OIDC 的其中一个用处就是可以用来构建一个单点登录系统(SSO)。
一个标准化的 SSO 场境
假设企业为了自身的运营需要分别部署了 2 个系统:「A、B」。两个系统上线时间不同,提供的业务服务不同,用户体系与认证逻辑不同。所以这两个系统是完全「互不相通」的。
现在需要在不影响现有系统逻辑的前提条件下,提供「一次登陆、多端有效」的功能。
经过构思,我们决定构建统一登陆中心;接着让两个系统都认可统一登陆中心的认证结果,来达到「最小化改动」现有系统的前提下实现 SSO 的功能。回顾上面的知识,OIDC 协议中的身份认证服务器就可以满足这一要求。
SSO 单点登录系统
注意这也是一个容易搞错的知识点:没有任何规定要求必须使用 OIDC 来构建 SSO。实际上你完全可以不使用 OIDC,或是使用 OIDC 的隐式许可类型流程来构建一个 SSO,这都完全取决于你的选择,况且互联网上运行的大部分 SSO 都并没有严格遵守 OIDC 规范。
OAuth2.1协议的变化
概述
一般Oauth2.0协议的授权码模式在统一认证中用的比较多,在实际的使用中,往往还会在获取令牌的时候,多一个参数,即client_secret来保证认证的安全。
在研究gitlab鉴权的时候,发现了一种Authorization code with Proof Key for Code Exchange (PKCE) 方式。
详情
相比OAuth2.0授权码模式,就是在获取授权码code的时候,多了code_challenge和code_challenge_method两个参数,在获取access_token令牌的时候,多了code_verifier一个参数。
其中
- code_verifier 是一种43-128个字符的随机字符串,由 A-Z, a-z, 0-9, -, ., _, and ~组成;
- CODE_CHALLENGE 是VERIFIER 通过SHA256摘要得到的一个Base64编码的字符串;
主要的变化
The major differences from OAuth 2.0 are listed below.
- PKCE is required for all OAuth clients using the authorization code flow
- Redirect URIs must be compared using exact string matching
- The Implicit grant (response_type=token) is omitted from this specification
- The Resource Owner Password Credentials grant is omitted from this specification
- Bearer token usage omits the use of bearer tokens in the query string of URIs
- Refresh tokens for public clients must either be sender-constrained or one-time use
- The definitions of public and confidential clients have been simplified to only refer to whether the client has credentials
主要点
引入PKCE
至于PKCE可以看一下详细设计
引入PKCE的主要原因就是因为OAuth2.0的授权码模式比较容易收到授权码拦截攻击。这个攻击就是恶意客户端拦截认证服务器返回的授权码,然后通过拦截的授权码获取到令牌,从而获取到用户的隐私信息; 授权码交换证明密钥 (PKCE) RFC 于 2015 年发布,它扩展了授权代码授权,以防止部分授权流通过非 TLS 连接发生时受到攻击。例如,在本机应用程序的组件之间。如果 TLS 存在漏洞,或者路由器固件已遭到入侵,并且正在欺骗 DNS 或从 TLS 降级到 HTTP,也可能发生此攻击。PKCE 需要将额外的一次性代码发送到 OAuth 服务器。这用于验证请求是否未被拦截或修改。
重定向URI使用精确匹配
在配置重定向地址时,往往支持通配符,这样肯定会很方便;比如有这样的一个场景,我们最终重定向的地址需要携带一些动态参数,这些动态参数往往被最终页面用来逻辑判断,从而执行一些业务逻辑;在认证过程中,如果携带了这些动态菜蔬,那么如果不支持同配符号,那么就与我们配置的重定向URIne都不匹配,导致认证失败;
但是,允许重定向 URI 的此类通配符匹配存在安全风险。如果重定向 URI 匹配是灵活的,攻击者可以将用户重定向到由他们控制的开放重定向服务器,然后重定向到恶意目标;虽然这需要以某种方式破坏请求,但对重定向 URI 使用精确匹配可以消除此风险,因为重定向 URI 始终是已知值。
限制刷新令牌
刷新令牌允许客户端检索新的访问令牌,而无需重新身份验证。如果需要访问资源的时间比访问令牌的访问时间更长,或者需要不频繁的访问(例如登录电子邮件数月或数年),这将非常有用。因此,它们的生存期通常比访问令牌更长。
如果它们被攻击者获取,攻击者可以随意创建访问令牌。显然,在这一点上,访问令牌保护的资源将不再安全。
OAuth 2.1 草案规范为刷新令牌提供了两个选项:它们可以是一次性使用的,也可以通过加密绑定到发送方。
一次性使用意味着在使用刷新令牌(称为刷新令牌 A)检索访问令牌后,该令牌将失效。当然,OAuth 服务器可以发送新的刷新令牌(称为刷新令牌 B)以及请求的访问令牌。在这种情况下,一旦新传送的访问令牌过期,客户端可以使用刷新令牌 B 请求另一个访问令牌,接收新的访问令牌和新的刷新令牌 C,依此类推。更改为一次性使用刷新令牌可能需要更改客户端代码,以便在每次刷新时存储新的刷新令牌。
另一个是确保 OAuth 服务器以加密方式将刷新令牌绑定到客户端。这个还没太理解。
总结
OAuth 2.1的最新规范旨在将OAuth 2.0中制定的变更和调整整合到一个文件中,以提高默认安全性和作为参考文档。
OAuth 2.1将删除某些风险较高的授权方式,如隐式授权和资源所有者密码凭证授权,并规定使用PKCE来加强授权码授权的安全性。
结合OAuth 2.0安全最佳实践文件,OAuth 2.1要求对重定向URI进行精确字符串匹配,避免在查询字符串中使用持有人令牌等。