快速入门:使用 Azure Active Directory 保护 Web APIQuickstart: Secure a Web API with Azure Active Directory

适用于:Applies to:
  • Azure AD v1.0 终结点Azure AD v1.0 endpoint

本快速入门将演示如何使用 passport-azure-ad 模块通过 Passport 保护 Restify API 终结点,以处理与 Azure Active Directory (Azure AD) 之间的通信。In this quickstart, you'll learn how to secure a Restify API endpoint with Passport using the passport-azure-ad module to handle communication with Azure Active Directory (Azure AD).

本快速入门的范围包括有关保护 API 终结点的注意事项。The scope of this quickstart covers the concerns regarding securing API endpoints. 本文未涉及有关登录和保留身份验证令牌的注意事项,这些问题由客户端应用程序负责。The concerns of signing in and retaining authentication tokens are not implemented here and are the responsibility of a client application. 有关客户端实现的详细信息,请查看使用 Azure AD 进行 Node.js Web 应用登录和注销For details surrounding a client implementation, review Node.js web app sign-in and sign-out with Azure AD.

GitHub 中提供了与本文相关的完整代码示例。The full code sample associated with this article is available on GitHub.

先决条件Prerequisites

开始前,请完成以下先决条件。To get started, complete these prerequisites.

创建示例项目Create the sample project

服务器应用程序需要安装几个包依赖项才能支持 Restify 和 Passport,以及传递给 Azure AD 的帐户信息。The server application requires a few package dependencies to support Restify and Passport as well as account information that is passed to Azure AD.

若要开始,请将以下代码添加到名为 package.json 的文件中:To begin, add the following code into a file named package.json:

{
  "name": "active-directory-webapi-nodejs",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "passport": "0.4.0",
    "passport-azure-ad": "4.0.0",
    "restify": "7.7.0"
  }
}

创建 package.json 后,在命令提示符下运行 npm install 来安装包依赖项。Once package.json is created, run npm install in your command prompt to install the package dependencies.

将项目配置为使用 Active DirectoryConfigure the project to use Active Directory

若要开始配置应用程序,可以通过 Azure CLI 获取几个特定于帐户的值。To get started configuring the application, there are a few account-specific values you can obtain from the Azure CLI.

az ad app create --display-name node-aad-demo --homepage http://localhost --identifier-uris http://node-aad-demo

create 命令的参数包括:The arguments for the create command include:

参数Argument 说明Description
display-name 注册的友好名称Friendly name of the registration
homepage 用户可在其中登录和使用应用程序的 URLUrl where users can sign in and use your application
identifier-uris 空格分隔的唯一 URI,Azure AD 可将其用于此应用程序Space separated unique URIs that Azure AD can use for this application

在连接到 Azure Active Directory 之前,需准备好以下信息:Before you can connect to Azure Active Directory, you need the following information:

NameName 说明Description 配置文件中的变量名称Variable Name in Config File
租户名称Tenant Name 要用于身份验证的租户名称Tenant name you want to use for authentication tenantName
客户端 IDClient ID 客户端 ID 是对 AAD 应用程序 ID 使用的 OAuth 术语。Client ID is the OAuth term used for the AAD Application ID. clientID

从 Azure powershell 的注册响应中,复制 appId 值并创建名为 config.js 的新文件。From the registration response in the Azure powershell, copy the appId value and create a new file named config.js. 接下来,添加以下代码,并将带有括号的标记替换为自己的值:Next, add in the following code and replace your values with the bracketed tokens:

const tenantName    = //<YOUR_TENANT_NAME>;
const clientID      = //<YOUR_APP_ID_FROM_CLOUD_SHELL>;
const serverPort    = 3000;

module.exports.serverPort = serverPort;

module.exports.credentials = {
  identityMetadata: `https://login.partner.microsoftonline.cn/${tenantName}.partner.onmschina.cn/.well-known/openid-configuration`, 
  clientID: clientID
};

有关各项配置设置的详细信息,请查看 passport-azure-ad 模块文档。For more information regarding the individual configuration settings, review the passport-azure-ad module documentation.

实现服务器Implement the server

passport-azure-ad 模块附带两种身份验证策略:OIDC持有者策略。The passport-azure-ad module features two authentication strategies: OIDC and Bearer strategies. 本文中实现的服务器使用“持有者”策略来保护 API 终结点。The server implemented in this article uses the Bearer strategy to secure the API endpoint.

步骤 1:导入依赖项Step 1: Import dependencies

创建名为 app.js 的新文件并在其中粘贴以下文本:Create a new file named app.js and paste in the following text:

const
      restify = require('restify')
    , restifyPlugins = require ('restify').plugins
    , passport = require('passport')
    , BearerStrategy = require('passport-azure-ad').BearerStrategy
    , config = require('./config')
    , authenticatedUserTokens = []
    , serverPort = process.env.PORT || config.serverPort
;

在此代码片段中:In this section of code:

  • 引用了 restify 和插件模块以设置 Restify 服务器。The restify and plugins modules are referenced in order to set up a Restify server.
  • passportpassport-azure-ad 模块负责与 Azure AD 通信。The passport and passport-azure-ad modules are responsible for communicating with Azure AD.
  • config 变量已使用在前一步骤中创建的 config.js 文件中的值初始化。The config variable is initialized with values from the config.js file created in the previous step.
  • authenticatedUserTokens 创建了一个数组,将用户令牌传递到受保护的终结点时,该数组会存储这些令牌。An array is created for authenticatedUserTokens to store user tokens as they are passed into secured endpoints.
  • serverPort 是从进程环境的端口或配置文件定义的。The serverPort is either defined from the process environment's port or from the configuration file.

步骤 2:实例化身份验证策略Step 2: Instantiate an authentication strategy

保护终结点时,必须提供一个策略来负责确定当前请求是否源自经过身份验证的用户。As you secure an endpoint, you must provide a strategy responsible for determining whether or not the current request originates from an authenticated user. 此处的 authenticatonStrategy 变量是 passport-azure-ad BearerStrategy 类的实例。Here the authenticatonStrategy variable is an instance of the passport-azure-ad BearerStrategy class. require 语句的后面添加以下代码。Add the following code after the require statements.

const authenticationStrategy = new BearerStrategy(config.credentials, (token, done) => {
    let currentUser = null;

    let userToken = authenticatedUserTokens.find((user) => {
        currentUser = user;
        user.sub === token.sub;
    });

    if(!userToken) {
        authenticatedUserTokens.push(token);
    }

    return done(null, currentUser, token);
});

此实现通过将身份验证令牌添加到 authenticatedUserTokens 数组(如果这些令牌不存在)来使用自动注册。This implementation uses auto-registration by adding authentication tokens into the authenticatedUserTokens array if they do not already exist.

创建策略的新实例后,必须通过 use 方法将它传递给 Passport。Once a new instance of the strategy is created, you must pass it into Passport via the use method. 将以下代码添加到 app.js,以便在 Passport 中使用该策略。Add the following code to app.js to use the strategy in Passport.

passport.use(authenticationStrategy);

步骤 3:服务器配置Step 3: Server configuration

定义身份验证策略后,可在 Restify 服务器中指定一些基本设置,并将其设置为使用 Passport 来获得安全性。With the authentication strategy defined, you can now set up the Restify server with some basic settings and set to use Passport for security.

const server = restify.createServer({ name: 'Azure Active Directory with Node.js Demo' });
server.use(restifyPlugins.authorizationParser());
server.use(passport.initialize());
server.use(passport.session());

将此服务器初始化并将其配置为分析授权标头,然后将其设置为使用 Passport。This server is initialized and configured to parse authorization headers and then set to use Passport.

步骤 4:定义路由Step 4: Define routes

现在可以定义路由,并确定要使用 Azure AD 保护哪个路由。You can now define routes and decide which to secure with Azure AD. 此项目包含两个路由,其中,根级别是开放的,/api 路由设置为要求身份验证。This project includes two routes where the root level is open and the /api route is set to require authentication.

app.js 中,为根级别路由添加以下代码:In app.js add the following code for the root level route:

server.get('/', (req, res, next) => {
    res.send(200, 'Try: curl -isS -X GET http://127.0.0.1:3000/api');
    next();
});

根路由允许所有请求通过,并返回一条消息,其中包含用于测试 /api 路由的命令。The root route allows all requests through the route and returns a message that includes a command to test the /api route. 相比之下,/api 路由已使用 passport.authenticate 锁定。By contrast, the /api route is locked down using passport.authenticate. 在根路由的后面添加以下代码。Add the following code after the root route.

server.get('/api', passport.authenticate('oauth-bearer', { session: false }), (req, res, next) => {
    res.json({ message: 'response from API endpoint' });
    return next();
});

此配置仅允许包含持有者令牌的、经过身份验证的请求访问 /apiThis configuration only allows authenticated requests that include a bearer token access to /api. session: false 选项用于禁用会话,要求在对 API 发出的每个请求中传递令牌。The option of session: false is used to disable sessions to require that a token is passed with each request to the API.

最后,通过调用listen 方法将服务器设置为在配置的端口上侦听。Finally, the server is set to listen on the configured port by calling the listen method.

server.listen(serverPort);

运行示例Run the sample

实现服务器后,可以通过打开命令提示符并输入以下命令来启动服务器:Now that the server is implemented, you can start the server by opening up a command prompt and enter:

npm start

运行服务器后,可将一个请求提交到服务器以测试结果。With the server running, you can submit a request to the server to test the results. 若要演示根路由返回的响应,请打开 bash shell 并输入以下代码:To demonstrate the response from the root route, open a bash shell and enter the following code:

curl -isS -X GET http://127.0.0.1:3000/

如果已正确配置服务器,则响应应如下所示:If you have configured your server correctly, the response should look similar to:

HTTP/1.1 200 OK
Server: Azure Active Directory with Node.js Demo
Content-Type: application/json
Content-Length: 49
Date: Tue, 10 Oct 2017 18:35:13 GMT
Connection: keep-alive

Try: curl -isS -X GET http://127.0.0.1:3000/api

接下来,可在 bash shell 中输入以下命令,测试要求身份验证的路由:Next, you can test the route that requires authentication by entering the following command into your bash shell:

curl -isS -X GET http://127.0.0.1:3000/api

如果已正确配置服务器,则服务器应在响应中返回 Unauthorized 状态。If you have configured the server correctly, then the server should respond with a status of Unauthorized.

HTTP/1.1 401 Unauthorized
Server: Azure Active Directory with Node.js Demo
WWW-Authenticate: token is not found
Date: Tue, 10 Oct 2017 16:22:03 GMT
Connection: keep-alive
Content-Length: 12

Unauthorized

创建安全 API 后,可以实现一个能够向 API 传递身份验证令牌的客户端。Now that you have created a secure API, you can implement a client that is able to pass authentication tokens to the API.

后续步骤Next steps