如何构建身份和访问管理(IAM)服务 part2
导语:在本文中,我们探讨了开发身份和访问管理服务的几种方法之间的差异。
2. 使用Auth0构建基于云的IAM服务
Auth0是一个基于云的平台,提供身份访问管理服务,帮助开发人员通过定制的IAM服务增强其应用程序。
当使用 Auth0 配置我们自己的 IAM 解决方案时,我们不需要托管和支持环境。此外,该服务还提供一些现成的用户表单,包括登录表单。您可以使用 Auth0 仪表板手动完成大部分配置。
Auth0免费提供了7000个用户的配额,所以根据我们的要求,我们可以免费集成。但是,这样的选项有局限性:
不支持多个企业。好的一面是,我们仍然可以创建多个 API,每个 API 具有单独的权限,并将它们视为企业。
不支持通过社交媒体服务进行连接。
与自定义数据库的连接不可用,这意味着没有旧数据库支持,也没有自我备份。
每月只有 1000 个机器对机器令牌可用,当我们想要以编程方式管理所有内容时,这可能是一个问题。
要开始使用 Auth0,您需要使用 Auth0 注册表创建管理员配置文件并转到 Auth0 仪表板。Auth0 管理仪表板提供了很多选项,但开始集成的最低要求包括以下内容:
已创建的应用程序
已创建的 API 和权限
Auth0 应用程序保存凭据(clientId和clientSecret,它们是身份验证和授权流程的一部分)并包含多个 API。
API分为两种类型:
系统API,供后端服务器或执行管理任务的受信任方使用。一般来说,任何可以通过Auth0仪表板完成的事情也可以通过这个API完成。系统 API 是默认创建的,包含管理 IAM 仪表板服务器端所需的一组预定义权限。
公共API,供前端和不受信任方使用。公共API可以手动创建,包含系统权限以及自定义权限。
我们将使用生成的基本 URL (https://dev-mt-ji-7q.us.auth0.com/) 和一个名为 https://example.com 的 API 创建一个应用程序,以供进一步用于演示目的。
2.1. 验证
要配置身份验证机制,您可以使用自定义或预定义的登录表单。让我们探讨一下这两个选项。
2.1.1. 使用自定义登录表单
使用自定义登录表单的过程称为资源所有者密码流程。它的工作原理如下:
用户单击“登录”并输入其登录名和密码。
任何应用程序都可以将用户的登录名和密码发送到我们的 Auth0 应用程序(/oauth/token 端点),其基本 URL 是在仪表板中创建的应用程序的 URL (https://dev-mt-ji-7q.us.auth0 .com/)。该 URL 将在创建过程中生成。
我们的 Auth0 应用程序检查登录名和密码。
我们的 Auth0 应用程序返回一个访问令牌和一个刷新令牌。
现在网站可以使用Access Token调用API服务器来访问资源。
对于资源所有者密码流程,我们需要启用密码授予类型,默认情况下禁用该类型。身份验证可以通过向 IAM 提供商(即 auth0 API 服务器)发出单个请求来实现。这种方法的唯一缺点是,发送到后端的凭据可以存储起来以供将来使用,然后再交换为访问令牌,这带来了潜在泄漏的可能性。
为了确保登录流程的安全,必须使用 MFA。但在使用 MFA API 之前,我们需要为应用程序启用MFA授权类型。为此,请转至应用程序 -> 选择我们的应用程序 -> 高级设置 -> 授予类型,然后选中密码和MFA复选框。
屏幕截图 1. 启用 MFA 授予类型
接下来要做的就是转到“设置” -> “API 授权设置” ,然后在“默认目录”字段中输入“用户名-密码-身份验证” ,然后单击“保存”。
屏幕截图 2. 填写默认目录字段
现在确保身份验证过程的一切都已准备就绪,让我们探索一下 MFA 的详细登录流程:
这样的流程涉及向用户发送挑战的用户验证器。所有请求都应从我们的外部 API 服务器发出。为了更好地理解上述方案,我们总结一下可能的场景:
如果禁用 MFA,则oauth/token端点将返回 JWT。下面显示的步骤 1 将直接引导至 JWT。
如果启用了 MFA 但尚未关联身份验证器,我们将执行以下步骤:
步骤1:请求token,之后服务器会收到 error mfa_required。
步骤 2:请求触发质询所需的用户身份验证器。
步骤3:将验证器与Auth0关联。API 服务器接收临时代码,用户接收一次性密码 (OTP),这是步骤 3a。
步骤 3b:使用服务器代码和用户的 OTP 代码重复令牌请求。
如果启用了 MFA 并且已关联身份验证器,我们将执行以下步骤:
步骤1:请求令牌,之后服务器将收到错误mfa_required。
步骤 2:请求用户身份验证器触发质询。
步骤3:将验证器与Auth0关联,服务器将收到错误,因为验证器已经关联。此处未触发步骤 3a,因为用户尚未获得 OTP。
步骤 4:请求用户质询,这会返回服务器的临时代码,将 OTP 发送给用户(步骤 4a),并使用服务器代码和用户的 OTP 代码重复令牌请求(步骤 4b)。在进一步的授权尝试中,可以省略步骤 3。
现在,让我们通过代码示例详细探讨这些步骤:
步骤 1.向/oauth/token端点发送请求。如果没有 MFA,此端点应立即返回 JWT。如果启用了 MFA,系统将提示用户通过挑战。
var axios = require("axios").default; var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/oauth/token', // https://dev-mt-ji-7q.us.auth0.com is generated after application creation headers: {'content-type': 'application/x-www-form-urlencoded'}, data: new URLSearchParams({ grant_type: 'password', // authentication by user password username: 'user@example.com', password: 'pwd', client_id: 'vFqyrkC6qz6gUyhPqL6rXBTjkY8jyN9a', // application client id client_secret: 'YOUR_CLIENT_SECRET', // application secret audience: 'https://example.com', // this is the API that was created earlier scope: 'email' }) }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
如果启用了 MFA,响应将包含 mfa_required错误和 mfa_token。然后,我们需要请求可用的 MFA 用户身份验证器。
步骤 2.使用以下代码获取 MFA 身份验证器:
var options = { method: 'GET', url: 'https://dev-mt-ji-7q.us.auth0.com/mfa/authenticators', headers: {authorization: 'Bearer MFA_TOKEN', 'content-type': 'application/json'} }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
现在,让我们获取一个包含用户可用身份验证器的数组:
[ { "id": "email|dev_NU1Ofuw3Cw0XCt5x", "authenticator_type": "oob", "active": true, "oob_channel": "email", "name": "email@address.com" } ]
步骤 3:每种身份验证器类型注册一次身份验证器。
以下是如何发出请求并将身份验证器与 API 关联:
var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/mfa/associate', headers: {authorization: 'Bearer MFA_TOKEN', 'content-type': 'application/json'}, data: {authenticator_types: ['oob'], oob_channels: ['sms'], phone_number: '+11...9'} }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
如果之前未启用身份验证器,我们将收到响应代码,并且用户将收到 OTP:
{ "authenticator_type": "oob", "binding_method": "prompt", "recovery_codes": [ "N3BGPZZWJ85JLCNPZBDW6QXC" ], "oob_channel": "sms", "oob_code": "ata6daXAiOi..." }
然后应将 OTP 和代码提供给 oauth/token(步骤 3b)。
如果身份验证器已关联,我们将收到错误并转至质询端点(请参阅步骤 4)。
步骤 4:向用户发送挑战:
var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/mfa/challenge', data: { client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', challenge_type: 'oob', authenticator_id: 'sms|dev_NU1Ofuw3Cw0XCt5x', mfa_token: 'MFA_TOKEN' } }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); }); When challenge is passed, the user receivers otp code (4a) and goes to step 4b
步骤 3b/4b:在上一步(3a 或 4a)之后,用户将收到应提供给/oauth/token的 OTP 代码:
var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/oauth/token', headers: { authorization: 'Bearer MFA_TOKEN', 'content-type': 'application/x-www-form-urlencoded' }, data: new URLSearchParams({ grant_type: 'http://auth0.com/oauth/grant-type/mfa-oob', client_id: 'vFqyrkC6qz6gUyhPqL6rXBTjkY8jyN9a', client_secret: 'YOUR_CLIENT_SECRET', mfa_token: 'MFA_TOKEN', oob_code: 'OOB_CODE', binding_code: 'USER_OTP_CODE' }) }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
作为响应,我们将收到 JWT:
{ "id_token": "eyJ...i", "access_token": "eyJ...i", "expires_in": 600, "scope": "openid profile", "token_type": "Bearer" } Note: Steps 3b and 4b are the same step in practice but have been split into different steps here to facilitate better understanding of the authorization flow (see scheme A login flow with MFA).
2.1.2. 使用预定义的登录表单
要使用预定义的登录表单,我们需要遵循授权代码流程。在这种情况下,我们从端点请求授权代码/authorize 并将其传递redirect_url 给 Auth0。系统将提示用户使用 Auth0 表单登录。成功登录后,用户将被重定向到我们的 API,该 API 将用授权代码交换 JWT 令牌。
获取代码的方法如下:
var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/authorize', headers: { authorization: 'Bearer MFA_TOKEN', 'content-type': 'application/x-www-form-urlencoded' }, data: new URLSearchParams({ response_type: 'code', client_id: 'vFqyrkC6qz6gUyhPqL6rXBTjkY8jyN9a' connection: null (Auth0 login form will be prompted), redirect_uri: 'link to application which will receive auth code', }) }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
以下是获取代码令牌的方法:
var options = { method: 'POST', url: 'https://dev-mt-ji-7q.us.auth0.com/oauth/token', headers: { authorization: 'Bearer MFA_TOKEN', 'content-type': 'application/x-www-form-urlencoded' }, data: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'vFqyrkC6qz6gUyhPqL6rXBTjkY8jyN9a', client_secret: 'YOUR_CLIENT_SECRET', code: 'authcode', }) }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
2.2. 授权
为了构建授权流程,我们可以使用express-oauth2-jwt-bearer SDK。
它需要在使用受众 (https://example.com) 和发行者 URL (https://dev-mt-ji-7q.us.auth0.com) 初始化的外部 API 服务器上创建中间件。中间件从标头开始检查承载。为了额外检查令牌中实体的权限,我们可以使用express-oauth2-jwt-bearer SDK配置中间件以进行范围检查。
const express = require('express'); const app = express(); const { auth, requiredScopes } = require('express-oauth2-jwt-bearer'); // Authorization middleware. When used, the Access Token must // exist and be verified against the Auth0 JSON Web Key Set. const checkJwt = auth({ audience: 'https://example.com', issuerBaseURL: 'https://dev-mt-ji-7q.us.auth0.com/', }); // This route needs authentication app.get('/api/private', checkJwt, function(req, res) { res.json({ message: 'Hello from a private endpoint! You need to be authenticated to see this.' }); }); const checkScopes = requiredScopes('get:users'); app.get('/api/users', checkJwt, checkScopes, function(req, res) { const auth = req.auth; const userData = await UserDAO.getById(auth.payload.sub); res.json(userData); }); app.listen(3000, function() { console.log }
身份验证过程很简单,因为我们可以完全依赖 Auth0 API 来检查令牌和权限。
2.3. RBAC
Auth0 提供两种方法来确保基于角色的访问控制:
使用授权核心
使用 RBAC 扩展
当我们使用授权核心时,Auth0 允许我们为在 Auth0 中创建的 API 启用 RBAC。要启用 RBAC 功能,我们可以使用仪表板或管理 API(以编程方式)。
使用授权核心方案我们需要做以下事情:
在 Auth0 应用程序中注册 API
在API中创建自定义权限(就Auth0而言,每个界定不同外部服务的API都有自己的一组权限)
创建自定义角色
为角色分配权限
为用户分配角色
直接为用户分配权限
我们可以通过仪表板或以编程方式创建和分配角色和权限。如果需要在授权过程中验证角色和权限,则可以将角色和权限包含在令牌中。
除了核心授权方法之外,RBAC 扩展还创建另一种类型的资源,称为组和规则:
规则是用作授权挂钩的任意 JavaScript 代码,可用于在对用户进行身份验证时扩展默认授权行为。启用的规则将在身份验证过程的最后一步执行。规则可用于在某些情况下拒绝特定用户的访问或向第三方服务索取信息。
组是用户的部门。
您可以在Auth0 网站上了解有关 RBAC 扩展的更多信息。
2.4. 暴力破解和机器人保护
暴力保护可保护企业免受单个 IP 地址攻击单个用户帐户的影响。当同一 IP 地址多次尝试以同一用户身份登录但失败时,暴力保护会执行以下操作:
向受影响的用户发送电子邮件
阻止可疑 IP 地址以该用户身份登录
暴力保护适用于所有用户,包括租户管理员。如果某个 IP 地址由于暴力保护而被阻止,它将保持被阻止状态,直到发生以下事件之一:
管理员删除该块
管理员提高登录门槛
30天通行证
受影响的用户选择电子邮件通知中的取消阻止链接(如果已配置)
受影响的用户更改了密码(在所有链接的帐户上)
您可以在 Auth0 仪表板中激活和自定义暴力破解和机器人保护功能。
2.5. 审核日志
Auth0 使您能够从仪表板查看所有与租户相关的日志,并通过钩子实时收集一些日志。
目前Hooks仅支持用户预注册、用户后注册等基本操作。要将所有可能的日志发送到外部存储,我们可以通过仪表板进行配置,并将日志移动到 AWS CloudWatch 服务。
2.6. 备份可能性
Auth0 会出于恢复目的进行自己的备份,但除了通过 API 调用将用户导出到文件之外,我们自己没有其他方法可以执行相同的操作。
在我们探索了如何在 Auth0 的帮助下为基于云的 IAM 服务配置功能后,让我们继续使用另一个名为 AWS Cognito 的云服务的类似示例。
在下一章节中,我们将从实战出发,介绍如何使用 AWS Cognito 构建基于云的 IAM 服务。
发表评论