保护服务器到服务器的API服务可能很棘手。 OAuth 2.0是将用户身份验证转移到另一个服务的绝佳方法,但是如果没有用户要身份验证怎么办? 在本文中,我将向您展示如何在用户上下文之外(也称为“客户端凭据流”)使用OAuth 2.0。
您可以使用第三方服务来管理您的授权,而不必为客户端(其他服务器)存储和管理API密钥。 这种工作方式是API客户端向OAuth服务器发送请求以获取API令牌。 该令牌随后随其请求从API客户端发送到您的API服务。 获得客户令牌后,您可以验证其有效性,而无需存储有关客户的任何信息。
验证您收到的API服务令牌的一种方法是将令牌转发到OAuth服务器以询问其是否有效。 此方法的缺点是,发送到服务器的每个API请求也需要发送到OAuth服务器的请求,这增加了您响应客户端的时间。 一种替代方法是使用一种称为本地验证的方法,该策略由JSON Web令牌(JWT)普及。 JWT在未加密的机器可读JSON中包含您的声明(客户端数据)。
当使用本地验证模式来验证API令牌(JWT)时,可以使用数学来验证以下内容:
您的API收到的令牌尚未被篡改您的API收到的令牌尚未过期令牌中编码的某些JSON数据片段就是您期望的
这样安全吗? 您可能想知道。 JWT包含三部分:标头、有效负载和签名。 标头和有效负载是简单的base64编码的字符串,可以轻松解密和读取。 签名使用标头中列出的算法以及私钥来创建标头和有效负载的哈希。 没有私钥就无法重新创建哈希,但是可以使用公钥进行验证。
在某种程度上,这就像驾照或护照。 这很难伪造,但是对别人来说很容易看到它,并看到您的姓名、出生日期和其他信息。 您可以扫描条形码,用黑光对其进行测试,或寻找水印以帮助验证其有效性。
尽管在概念上相似,但是有效的JWT实际上很难建立。 具有足够技能的人可以创建令人信服的驾驶执照,但是如果没有私钥,则可能需要花费现代计算机几年的时间才能强制使用有效的JWT签名。 令牌也应具有到期时间。 虽然是可配置的,但默认设置为一小时。 这意味着,如果客户端需要向您的API服务器提出新请求,则需要每60分钟请求一个新令牌。 万一您的令牌被盗,这是一个额外的安全层。 谁知道? 也许那里有一台量子计算机可以在几个小时内重新创建签名。
现在,您了解了OAuth 2.0客户端凭据流程的基本原理,让我们构建一个使用客户端凭据和Okta的Node API。
简而言之,我们使身份管理比过去更容易、更安全、更可伸缩。Okta是一个API服务,允许您创建、编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。我们的空气污染指数可让你:
注册一个永久免费的开发者帐户 ,完成后,请返回以了解有关在Node中构建安全API的更多信息!
为了入门,我将向您展示如何在Node中创建基本API。 Node在名为package.json
的文件中保留了依赖项列表以及其他元数据。
假设您已经安装了Node,请为您的API服务器创建一个新文件夹。 然后,您可以使用npm
为您生成package.json
。 命令npm init
将提示您输入一些信息,但是您可以继续Enter
以遵循默认值。
$ mkdir client-credentials-flow
$ cd client-credentials-flow
$ git init
$ npm init
在Node中启动并运行API服务器的最快方法是使用Express。 您可以使用命令npm install [email protected] --save
将Express添加为依赖npm install [email protected] --save
。 这将创建一个名为node_modules
的文件夹,在该文件夹中下载express及其依赖的所有内容,然后您的应用可以使用这些文件夹。 为了使开发更快,您还可以添加一个名为nodemon
的dev依赖nodemon
,只要您更改代码, nodemon
将重新启动服务器。 要添加dev-dependency,请使用-D
标志: npm install -D [email protected]
。
在构建Node应用程序时,通常需要忽略将node_modules
文件夹存储在git node_modules
中。 您可以通过将node_modules
添加到.gitignore
文件中来实现。
echo node_modules >> .gitignore
软件包管理器还将包含一个文件(例如package-lock.json
或yarn.lock
),以便在另一台计算机上(使用npm install
或yarn
)下载node_modules
时,将下载相同版本的文件。 这有助于防止服务器之间的任何不一致,并使您不知道为什么某些东西可以在您的计算机上正常工作,而在生产环境中却无法正常工作。 确保也将该文件提交到您的git repo:
$ git add .
$ git commit -m "Adding package files."
您还可以将脚本添加到package.json
文件夹中以运行这些命令。 使用命令node .
创建一个start
脚本node .
( .
告诉它以main
身份运行package.json
列出的脚本,默认情况下为index.js
。您还希望使用nodemon *.js node .
命令创建dev
脚本。。命令行依赖项,像nodemon
、在道路节点脚本中运行时,您现在可以运行这些命令。 npm start
或npm run dev
你。 package.json
文件现在应该是这个样子:
package.json
{
"name": "client-credentials-flow",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon *.js node .",
"start": "node .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
},
"devDependencies": {
"nodemon": "^1.17.5"
}
}
现在,对于最基本的“ Hello World”快递应用程序:
index.js
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))
仅此而已! 要启动它,请在终端窗口中键入npm run dev
。 您可以在进行更改时保持此运行状态,它将自动重新启动以反映新的更改。 现在,在浏览器中转到http://localhost:3000
(或在带有curl http://localhost:3000
的命令行上),您应该看到Hello World!
回响。
现在保护应用程序。 这是您需要设置OAuth 2.0服务的地方。 Okta是一项基于云的服务,可让开发人员轻松安全地存储OAuth 2.0令牌,用户帐户和用户数据,然后将它们与一个或多个应用程序连接。 Okta还提供了适用于多种语言(包括Node)的库,以使它们的API非常易于开发人员集成到各种应用程序中。
您可以使用Okta快速轻松地设置服务器到服务器的身份验证。 如果您还没有帐户,请注册一个免费的Okta Developer帐户 。 注册后,您将获得一个唯一的Okta Org URL(例如https://{yourOktaDomain}
)和一封电子邮件,以激活您的新帐户。
为了使客户端到服务器的身份验证工作,您将需要两个部分:授权服务器和测试客户端/应用程序。
客户端可以在授权服务器上请求令牌以在您的API服务器上使用。 在Okta仪表板内,单击标题中的API选项卡,然后选择“ 授权服务器”选项卡。 点击添加授权服务器 ,然后为您的服务器提供有用的名称和描述。 Audience
应该是将使用令牌的服务器的绝对路径。
创建授权服务器后,将需要一个范围供您的客户端访问。 单击“ 范围”选项卡并添加范围。 您可以拥有许多这样的功能,这可以帮助定义正在使用API的哪些部分,甚至是谁在使用它。
现在您有了作用域,还需要指定一些规则来说明谁可以访问它。 单击访问策略选项卡,然后创建一个新策略。 现在,只允许访问All clients
。 然后单击“ 添加规则”并为其命名。 由于这仅用于客户端凭据,因此请删除其他代表用户执行的Authorization Code
类型(“ Authorization Code
,“ Implicit
”和“ Resource Owner Password
),因此唯一的授权类型是“ Client Credentials
。 除此之外,现在就使用默认设置。
返回“ 设置”选项卡,记下Issuer 。 这是客户端将用于请求令牌的地址,以及您的API服务器将用来验证这些令牌有效的地址。
在您的Okta仪表板中,单击顶部标题中的“ 应用程序 ”。 应用程序也称为客户端,因此您可以在其中创建测试客户端。 单击添加应用程序,然后选择服务 (机器对机器)。 它唯一需要的信息是名称,因此您可以使用诸如Test Client
类的东西。 这将为您提供客户的凭据(在此测试用例中,就是您)。
现在,您已经完成了所有的难题,因此只有经过身份验证的用户才能收到心爱的“ Hello World”欢迎消息,其他所有人都会出错。
您需要安全地存储凭据。 一种方法是在本地保存未存储在git中的文件(如果您的代码是开源的,则特别有用,但是无论如何还是一件好事)。 这也使您可以将相同的代码用于多个应用程序(例如,开发和生产环境)。
继续并创建一个.env
文件,其中包含授权服务器中的颁发者和测试应用程序中的客户端凭据。 确保将其添加到您的.gitignore
文件中,这样就不会将其添加到您的git repo中: echo .env >> .gitignore
。 您的.env
文件应如下所示:
.env
ISSUER=https://{yourOktaDomain}/oauth2/abcdefg1234567
DEFAULT_SCOPE=such_scope
TEST_CLIENT_ID=client-id
TEST_CLIENT_SECRET=client-secret
代码读取.env
文件的快速方法是使用名为dotenv
的库。 您可以使用npm install dotenv
安装它。 然后将require('dotenv').config()
到index.js
的第一行。 您希望它是运行的第一件事,以便其余代码可以访问这些环境变量。
Okta提供了一个Node库来帮助验证JSON Web令牌。 当它第一次看到验证令牌的请求时,它将通过您的授权服务器获取公共密钥Okta。 尽管可以配置,但默认情况下它将保留一个键一个小时。 如果输入的令牌无法验证,它将与Okta一起检查是否有新的密钥要使用。 如果仍然无法验证,则库将引发错误。 您可以使用npm install @okta/[email protected]
软件包。
您需要为软件包提供JWT。 您可以告诉客户如何提供令牌,这可以通过多种方式完成。 通常的做法是在HTTP请求中使用Authorization
标头,该请求通常看起来像Bearer MG9h...NhOq==
。 修改您的/
端点以查找令牌,并使用Okta进行验证。
index.js
const OktaJwtVerifier = require('@okta/jwt-verifier')
const oktaJwtVerifier = new OktaJwtVerifier({
issuer: process.env.ISSUER,
})
app.get('/', async (req, res) => {
try {
const { authorization } = req.headers
if (!authorization) throw new Error('You must send an Authorization header')
const [authType, token] = authorization.split(' ')
if (authType !== 'Bearer') throw new Error('Expected a Bearer token')
await oktaJwtVerifier.verifyAccessToken(token)
res.json('Hello World!')
} catch (error) {
res.json({ error: error.message })
}
})
尝试再次访问http://localhost:3000
。 由于您未通过身份验证,这一次您会收到一条错误消息。
如果您不熟悉相对较新的async/await
语法,这对您来说可能有点奇怪。 这里发生的是该函数被标记为async
,因此它将始终返回Promise
。 当它看到await
关键字时,该函数的其余部分将暂停,直到响应返回。 同时,释放了主线程以供其他JavaScript代码执行。
在此示例中, verifyAccessToken
如果无法立即验证令牌, verifyAccessToken就会
向Okta发送请求。 如果您在代码中的其他位置具有setInterval
,则在等待Okta的响应时,该代码仍然可以执行。
verifyAccessToken
完成后,如果令牌无效,则会抛出错误。 因此,如果通过该行而没有引发错误,则可以安全地假设客户端已通过验证,并且可以发送“ Hello World”消息。 如果您需要有关客户端的更多信息,则可以使用const jwt = await oktaJwtVerifier.verifyAccessToken(token)
从验证程序获得响应。
现在,您可以看到在没有正确身份验证的情况下浏览器出现错误,但是我没有向您显示仍然可以正确验证自己。 为了从授权服务器获取令牌,您可以编写一个简单的Node脚本。 使用本机节点request
有点繁琐,因此您可以使用request-promise
库,该库将允许您继续使用promise和不错的async/await
语法。 您还将需要btoa
,它将字符串转换为base64。
npm install [email protected] [email protected]
test.js
require('dotenv').config()
const request = require('request-promise')
const btoa = require('btoa')
const { ISSUER, TEST_CLIENT_ID, TEST_CLIENT_SECRET, DEFAULT_SCOPE } = process.env
const test = async () => {
const token = btoa(`${TEST_CLIENT_ID}:${TEST_CLIENT_SECRET}`)
try {
const { token_type, access_token } = await request({
uri: `${ISSUER}/v1/token`,
json: true,
method: 'POST',
headers: {
authorization: `Basic ${token}`,
},
form: {
grant_type: 'client_credentials',
scope: DEFAULT_SCOPE,
},
})
const response = await request({
uri: 'http://localhost:3000',
json: true,
headers: {
authorization: [token_type, access_token].join(' '),
},
})
console.log(response)
} catch (error) {
console.log(`Error: ${error.message}`)
}
}
test()
现在,在您的应用仍在端口3000上运行的情况下,使用node test.js
运行测试。 这将向Okta发送请求以获取令牌,然后将该令牌转发到您的API服务器并打印结果。 您应该得到一个不错的“ Hello World”问候!
您有一个测试客户端,但在现实世界中,您可能希望让人们注册您的API,而无需您登录Okta并手动为其创建客户端。 您也可能不希望每个人都共享相同的客户端ID和机密,以便例如跟踪谁在发出什么请求。
Okta提供了一个API,可让您自动执行各种任务。 其中之一是创建新的应用程序。 Okta还具有一个Node库,使其非常简单。 为了使您的应用程序可以通过Okta进行身份验证,您需要一个API令牌。 登录到您的信息中心,然后从标题的API下拉列表中选择令牌 。 单击创建令牌,并为其赋予一个有意义的名称。 然后,这一次将为您提供令牌-如果丢失,则需要创建另一个令牌。 继续,并将其作为TOKEN
添加到您的.env
文件中。
使用npm install @okta/[email protected]
安装Okta Node SDK。 它需要您的组织URL,因此您也应该将其添加到.env
文件中。 然后在index.js
创建一个新路由来注册新客户端。
index.js
const okta = require('@okta/okta-sdk-nodejs')
const oktaClient = new okta.Client({
orgUrl: process.env.ORG_URL,
token: process.env.TOKEN,
})
app.get('/register/:label', async (req, res) => {
try {
const application = await oktaClient.createApplication({
name: 'oidc_client',
label: req.params.label,
signOnMode: 'OPENID_CONNECT',
credentials: {
oauthClient: {},
},
settings: {
oauthClient: {
grant_types: ['client_credentials'],
application_type: 'service',
},
},
})
const { client_id, client_secret } = application.credentials.oauthClient
res.json({
client_id,
client_secret,
request_token_url: `${process.env.ISSUER}/v1/token`,
})
} catch (error) {
res.json({ error: error.message })
}
})
现在,您可以转到http://localhost:3000/register/Awesome+App+Name
(在您的浏览器中可以使用)来创建新客户端。 第一次去那里时,它应该为您提供客户ID和密码,并提醒您在哪里申请令牌。 您可以使用.env
新客户端ID替换以前的客户端ID和密码,然后重新运行test.js
以查看该客户端现在也可以使用。
如果您重新登录Okta Developer Console,您将看到“ Awesome App Name”已添加为应用程序。
请记住, 这是一个测试……这只是一个测试。 您可能不想让任何人未经任何类型的验证就公开注册API密钥。 这可能是实施某种CAPTCHA或要求用户身份验证以获取API密钥的好地方。 一旦拥有API密钥,他们便可以在其应用程序中使用该密钥,而无需进一步的用户身份验证。
希望我已经向您展示了,为您的Node API提供出色的安全性确实很容易。 Okta在保护您的应用程序方面提供了更多的选择。 如果您想了解有关Node、Okta和OAuth 2.0客户端凭据的更多信息,请查看其他一些资源,或浏览Okta开发人员博客的其余部分:
与往常一样,您可以在下面的评论中提供反馈或问题,或者在Twitter @oktadev上与我们联系 。 我们期待您的回音!
原文链接: https://www.sitepoint.com/secure-a-node-api-with-oauth-2-0-client-credentials/