近期所在部门基本完成了 IDaaS(身份即服务) 系统的改造,故将所涉及到的知识点总结成本文。
SaaS IAM = IDaaS
先说结论,所谓的 IDaaS 其实是 SaaS 加上 IAM 。
1. SaaS 软件即服务
软件即服务:Software as a Service,缩写就是我们常说的 SaaS 。
即服务(aaS) 通常是指由别人(一般指云服务厂商)提供的服务,它可以让个人或企业专注于自身更重要的业务。
在 SaaS 这种软件交付模式下,软件不再需要复杂的安装部署过程,只需通过网络连接就可直接使用,软件及其数据托管在云服务厂商中,厂商将全程负责处理软件更新、漏洞修复等维护工作。用户通常通过 Web 浏览器或 API 连接就可以使用软件。
例如腾讯云直播的 SaaS 方案,以及 Microsoft Office 365 其实也是一种典型的针对个人用户的 SaaS 应用。
和 SaaS 对应的还有另外两种交付模式:IaaS 和 PaaS 。
交付模式对比图[1]
上图的湖蓝色模块代表企业需要自行实现的服务,而红色模块代表云服务厂商所提供的即用服务。
On-site ,所有的一切都在用户本地部署。
IaaS ,全称 Infrastructure as a Service ,基础设施即服务。主要提供一些基础资源,包括实际的服务器、网络、虚拟化和存储。例如我们买了个腾讯或阿里云服务器,在上面自行搭建所需环境并部署我们的软件。
PaaS ,全称 platform as a service ,平台即服务。主要面向开发人员,云服务厂商在提供基础设施之余,还提供业务软件的运行环境,以集成解决方案或服务的形式将该平台交付给用户。例如腾讯云的微服务平台 TSF 提供了基于 Spring Cloud 和 Service Mesh 两种微服务架构的支持。
PaaS 其实也是 SaaS 模式的一种应用,但区别在于,SaaS 面向的不是软件的开发人员,而是软件的最终用户。
有个生动形象的 Pizza as a Service[2] 的例子可以阐述 SaaS,PaaS,IaaS 三者的区别。
对应上面,大抵意思就是:
On-site ,在家自己做披萨
IaaS ,买速食披萨回家自己做着吃
PaaS ,叫外卖将披萨送到家里吃
SaaS ,在披萨店吃披萨
2. IAM 身份和访问管理服务
IAM 全称 Identity and Access Management ,是一种 Web 服务,可以帮助用户安全地控制对资源的访问。通俗点讲就是可以使用 IAM 来控制谁通过了身份验证(准许登录)并获得授权(拥有权限)来使用资源。详细可以查看 AWS IAM 文档[3]。
与聘请一个保安在大厦门口相似,企业的应用程序接入 IAM 服务后,就可以“高枕无忧”,IAM 会帮你控制谁有权限可以访问应用。
3. IDaaS 身份即服务
来到本文的主角,身份即服务:Identity as a Service 。
Identity 就是 IAM 中的身份概念,as a Service 就是开篇介绍的 SaaS 。
IDaaS 实际上就是一个基于 SaaS 模式的 IAM 解决方案,也就是云上的身份和访问管理服务,完全由受信任的第三方云服务厂商托管和管理。
它允许企业使用单点登录、身份验证和访问控制来提供对任意接入的已实现标准协议应用的安全访问。
根据 IDaaS 厂商提供的功能,某种程度下,你甚至可以认为 IDaaS 就是 IAM 。
国内目前了解到的 IDaaS 厂商有,腾讯云 IDaaS[4] ,阿里云应用身份服务 IDaaS[5] ,Authing[6] 。在云计算、云原生领域,很多时候想要了解一个产品,其实可以去腾讯云或阿里云多瞄瞄多参考参考他们的现成方案。
IDaaS 的五统能力
IDaaS 的功能可以合称为 5A 能力[7] :Account、Authentication、Authorization、Application、Audit。
但我愿称其为五统能力:
1. 统一账号管理
Account 模块的管理,这块容易理解。你想最基本的一块,应用程序要想实现单点登陆的话,
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 A 系统使用了 user1 登录成功后,B 系统随之也以 user1 的身份登录成功了。例如淘宝 taobao.com 和天猫 tmall.com 两个站点。
那其账号是不是必须得统一放在一处(IDaaS)进行会话管理。
不过通常由于业务需要,应用系统和 IDaaS 还得做一次账号同步(SCIM / LDAP 方式)。
2. 统一身份认证
身份认证叫做 Authentication 。更形象化就是指你在应用上通过输入账号密码、验证码或扫码等方式进行登录的这个过程。
而这个过程 IDaaS 所使用的认证协议会帮你做了,而且把登录页面都给做了,直接喂到你嘴里,你应用只需要重定向过来即可。
身份认证协议有很多种:OIDC / SAML 等。下文我会再着重讲述 OIDC 协议。
3. 统一授权管理
Authorization 是授权,切忌别和认证 Authentication 混淆。
授权是指客户端经过身份认证成功后,能够以有限的权限去访问服务端资源的一种机制。
IDaaS 的授权架构一般会选用基于角色的权限访问控制(RBAC)。
常用的授权模式的话有基于 OAuth 2.0 框架的授权码模式,这部分我也会在下文的 OIDC 章节重点讲诉。
4. 统一应用管理
Application 应用包括企业接入进来的自身应用以及 IDaaS 所提供的一些可选模板应用(如 GitLab、Grafana 等)。
IDaaS 自身提供统一标准规范,对应用统一管理,例如可以控制应用是否支持单点登录,控制应用的可访问成员等。
5. 统一审计管理
Audit 审计,没什么好讲的,业务角度,代码埋点,Webhook 等方式记录下应用和用户相关的操作日志,便于管理员分析系统的日常操作与安全事件数据。
OIDC 身份认证协议
接下来将进入本文核心内容,讲述 IDaaS 背后支撑着认证和授权能力的基石:OAuth 2.0 和 OIDC 协议。
1. OAuth 2.0
OAuth 即 Open Authorization ,开放授权。
OAuth 2.0 (RFC 6749[8] )是一个授权标准协议,可以使第三方应用获得对资源服务的有限访问。
根据 OAuth 2.0 协议规范,定义了四个角色:
- 资源所有者(Resource owner):能够授予对受保护资源访问权限的实体。例如应用的用户是资源的所有者,可以授权其他人访问他的资源。当资源所有者是一个人时,它被称为最终用户。
- 资源服务器(Resource server):存储受保护资源的服务器,能够接受并使用访问令牌来响应受保护的资源请求。就是资源服务器接受 Access Token,然后验证它拥有的权限,最后返回对应的资源。这个资源服务器一般是应用本身。
- 授权服务器(Authorisation server):服务器向客户端(即应用)颁发访问令牌来验证资源所有者并获得授权。即负责颁发 Access Token 的服务器,例如 IDaaS 就是一个授权服务器。
- 客户端(Client):需要获取访问令牌以访问资源服务器的应用。经过授权后,授权服务器为客户端颁发 Access Token。后续客户端可以携带这个 Access Token 到资源服务器那访问用户的资源。
在 OAuth 2.0 中一个应用可能既是 Resource server ,也是 Client ,具体是什么角色,取决于应用工作的场景。
概念可能有点难嚼,还请慢咽。
这四个角色一直在围绕着一个叫 Access Token 的东西在转圈圈。
Access Token 也就是访问令牌,它用于允许应用访问一个资源 API。用户认证授权成功后,授权服务器会签发 Access Token 给应用。应用后续需要携带 Access Token 访问资源 API,资源服务 API 会检验 Access Token 是否有权限访问,从而决定是否返回对应资源。
而 Access Token 本质上只是一串随机字符串,并不能从中获取到任何信息,检验 Access Token 的步骤还需要资源服务器将它转发到授权服务器上进行解析验证。
了解完 Access Token 之后,我们来关注一下客户端调用方是如何获取到它的,也就是授权模式的选择。
- 授权码模式(Authorization Code):适用于具有完整前后端的传统 Web 应用以及移动或桌面端应用。
- 隐式模式(Implicit):适用于没有后端的基于浏览器(JavaScript)的纯前端应用。
- 密码模式(Resource Owner Password Credentials):适用于资源服务器和客户端之间高度信任的情况下,例如自家应用使用自家的资源。
- 客户端凭证模式(Client Credentials):适用于没有前端参与,纯后端交互的情况,期间没有用户的参与,客户端自己就是资源所有者。
- ......
我们重点关注授权码模式和客户端凭证模式,这两个是最常用的。
先看授权码模式,也叫 code 换 token 模式,我们以 Stack Overflow 使用 GitHub 登录为例(在这个过程中 Stack Overflow 是客户端,GitHub 是资源服务器,提供邮箱头像等资源信息,同时 GitHub 也是授权服务器,会颁发 token 给客户端)。
第一步,点击登录后需要跳转到授权服务器地址,即 GitHub 的地址,并且必须在 URL 上携带 client_id
和 redirect_uri
以及 scope
等信息。
https://github.com/login/oauth/authorize?
client_id=01b478c0264a1fbd7183&
redirect_uri=https://stackauth.com/auth/oauth2/github&
response_type=code&
scope=user:email&
state=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
client_id
可以让 GitHub 知道是哪个客户端在请求资源,这里的01b478c0264a1fbd7183
就是 Stack Overflow 在 GitHub 中注册的客户端 IDredirect_uri
是授权成功或失败后的回调地址response_type=code
表示使用授权码模式授权scope
表示应用正在请求哪些权限state
是一个随机字符串用来防止 CSRF 攻击
继续第二步,当我们点击授权按钮后,这时授权服务器即 GitHub 会将浏览器重定向到第一步的 redirect_uri
参数指定的网址。并且跳转时,会携带一个授权码 code 参数(由 GitHub 后台生成的)以及 state 参数。
https://stackauth.com/auth/oauth2/github?
code=1efc47a278d10a04f88e&
state=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
第三步,由于浏览器触发请求了 redirect_uri
,所以 Stack Overflow 网站后台可以接收到请求并拿到授权码 code 和 state ,这时就可以将 state 和第一步生成的 state 对比以防止 CSRF 和其他相关攻击。
第四步,Stack Overflow 可以拿着 client_id
、 client_secret
、 code
跟 GitHub 交换令牌,GitHub 收到请求以后就会校验 code 并颁发访问令牌(code 是有过期时间的,并且是一次性的)。
// Request
POST https://github.com/login/oauth/access_token?
code=1efc47a278d10a04f88e&
client_id=01b478c0264a1fbd7183&
client_secret=xxxxxx
// Response
Accept: application/json
{
"access_token":"gho_16C7e42F292c6912E7710c838347Ae178B4a",
"scope":"user:email",
"token_type":"bearer"
}
最后第五步,Stack Overflow 使用 GitHub 颁发的访问令牌跟 GitHub 获取到所需的资源(用户邮箱地址),然后就走自己的业务流程了,一般就是重定向回首页。
完整的流程可以看 GitHub 的 Creating an OAuth App Docs[9]。
另一个客户端凭证模式就相对简单了,毕竟只是纯后端交互。
例如一个 B 应用拥有很多 photo 资源,即 B 为资源服务器(假设同时也是授权服务器),A 客户端想要获取 B 的 photo 资源。
第一步,A 应用直接使用 client_id
和 client_secret
向 B 申请资源
https://b.com/oauth/token?
grant_type=client_credentials&
scope=photo&
client_id=xxx&
client_secret=xxx
response_type=client_credentials
表示使用客户端凭证模式授权
第二步,B 作为授权服务器验证 A 客户端的 client_id
和 client_secret
是否合法,然后颁发访问令牌。
第三步,A 客户端携带访问令牌向 B 资源服务器获取 photo 资源。
这期间并没有用户的参与,A 客户端自己就相当于一个 “用户”。
2. OpenID Connect
讲完 OAuth 2.0 授权,来到 OIDC 认证协议。
如标题,OIDC 的全称是 OpenID Connect[10] ,是一个基于 OAuth 2.0 的认证 授权(OAuth 2.0 提供的能力)协议。
OIDC 可以理解为 OAuth 2.0 的超集,在 OAuth 2.0 之上实现了更多的标准,例如定义了一系列的 EndPoint 。还有一些规范诸如 Session Management[11] ,Front-Channel Logout[12] ,Back-Channel Logout[13] 等。
OIDC 之所以有认证的功能,是因为在 OAuth 2.0 颁发 Access Token 的基础上,多颁发了一个 ID Token (用户的身份凭证),和 Access Token 是一个随机字符串不同的是,ID Token 是一个 JWT Token 。
关于 JWT 可以看我这篇文章:你可能没那么了解 JWT 。
ID Token 自身包含了一些用户的基本信息,而且由于 JWT 的防篡改性,让客户端不需要再向授权服务器进行身份验证,就能直接用 ID Token 来进行身份验证。即使 ID Token 包含的用户信息不够,也可以调用 OIDC 定义的 UserInfo EndPoint(用户信息接口) 来获取更多的用户信息。
OIDC 协议定义的名词和 OAuth 2.0 协议定义的稍微有些出入:
- OpenID Provider ,简称 OP :相当于 OAuth 2.0 中的授权服务器,除了负责颁发 Access Token ,还会颁发 ID Token 。例如 IDaaS 就是一个 OP 。
- Relying Party ,简称 RP :代指 OAuth 2.0 中的客户端。
- End User ,简称 EU :即用户。
最后 OIDC 的授权流程与 OAuth 2.0 是一样的,主要区别在于 OIDC 授权流程中会额外返回 ID Token。
云原生下的 OIDC Provider 服务
IDaaS 的认证和授权使用了 OIDC/OAuth 2.0 。说白了,要搭建 IDaaS 得先搭建一个授权服务器 OpenID Provider (更多时候称之为 OIDC Provider )。
但即便完全了解 OIDC 协议,自行实现一套完整的 OIDC Provider 依旧十分繁琐,好在云原生环境下孕育出了很多优秀的项目。本文会简单介绍一下 Go 语言生态下的 dexidp/dex 和 ory/hydra ,感兴趣的可以自行到其官网详细了解。
1. dexidp/dex
Dex[14] 作为 CNCF 的一个 sandbox 项目,是一个具有可插拔连接器的 OIDC 和 OAuth 2.0 提供商。
它通过 "连接器" 的身份来充当其他身份提供商的门户,可以将身份验证推送到 LDAP 服务器、SAML 提供商或 GitHub、Google 和 Active Directory 等其他一些成熟的身份提供商中进行验证。客户端只需写一次认证逻辑就可以与 Dex 对接,然后 Dex 来处理特定后端的协议。
2. ory/hydra
ORY Hydra[15] 是一个 OAuth 2.0 和 OpenID Connect 提供者。
特别一说的是 Hydra 所实现的 OAuth 2.0 协议并不依赖 Go 标准库提供的,而是自实现的 fosite[16] 。
另外 ORY Hydra 还提供了一个 5 分钟的快速搭建教程。
参考资料
[1]交付模式对比图: https://www.redhat.com/zh/topics/cloud-computing/what-is-saas
[2]Pizza as a Service: https://m.oursky.com/saas-paas-and-iaas-explained-in-one-graphic-d56c3e6f4606
[3]AWS IAM 文档: https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/introduction.html
[4]腾讯云 IDaaS: https://cloud.tencent.com/product/tcid
[5]阿里云应用身份服务 IDaaS: https://www.aliyun.com/product/idaas
[6]Authing: https://www.authing.cn/
[7]5A能力: https://help.aliyun.com/document_detail/167902.html
[8]RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749
[9]Creating an OAuth App Docs: https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app
[10]OpenID Connect: https://openid.net/specs/openid-connect-core-1_0.html
[11]Session Management: https://openid.net/specs/openid-connect-session-1_0.html
[12]Front-Channel Logout: https://openid.net/specs/openid-connect-frontchannel-1_0.html
[13]Back-Channel Logout: https://openid.net/specs/openid-connect-backchannel-1_0.html
[14]Dex: https://dexidp.io/
[15]ORY Hydra: https://www.ory.sh/hydra/docs/next/
[16]fosite: https://github.com/ory/fosite