如何:通过 OAuth WRAP 协议从 ACS 请求令牌

应用于

  • Microsoft Azure Active Directory 访问控制(也称为访问控制服务或 ACS)

概述

当 Web 应用程序和服务使用 ACS 处理身份验证时,客户端必须获取 ACS 颁发的安全令牌才能登录到应用程序或服务。 若要获取此 ACS 颁发的令牌 (输出令牌) ,客户端必须直接使用 ACS 进行身份验证,或者向其标识提供者颁发的安全令牌发送 ACS (输入令牌) 。 ACS 验证此输入安全令牌,通过 ACS 规则引擎处理此令牌中的标识声明,计算输出标识声明,并颁发输出安全令牌。

本主题介绍通过 OAuth WRAP 协议从 ACS 请求令牌的方法。 通过 OAuth WRAP 协议传输所有令牌请求都是通过 SSL 传输的。 ACS 始终通过 OAuth WRAP 协议发出简单的 Web 令牌 (SWT) ,以响应格式正确的令牌请求。 通过 OAuth WRAP 协议发出的所有令牌请求都会发送到 HTTP POST 中的 ACS。 可以从任何能够生成 HTTPS FORM POST 的平台(.NET Framework、Windows Communication Foundation (WCF) 、Silverlight、ASP.NET、Java、Python、Ruby、PHP、Flash 和其他平台)通过 OAuth WRAP 协议请求 ACS 令牌。

下表列出了通过 OAuth WRAP 协议请求 ACS 颁发的 SWT 令牌的三种受支持的方法。

通过 OAuth WRAP 协议从 ACS 请求令牌的三种方法

令牌请求方法 说明

密码令牌请求

最简单的方法要求客户端通过 OAuth WRAP 协议将用户名和密码直接从服务标识发送到 ACS 进行身份验证。

SWT 令牌请求

此方法要求客户端通过 OAuth WRAP 协议将可以使用服务标识对称密钥或标识提供者对称密钥签名的 SWT 令牌发送到 ACS 进行身份验证。

SAML 令牌请求

主要用于 Active Directory 联合身份验证服务 (AD FS) 2.0 集成,安全断言标记语言 (SAML) 方法要求客户端通过 OAuth WRAP 协议将签名的 SAML 令牌发送到 ACS 进行身份验证。 此方法允许客户端使用企业标识通过 ACS 进行身份验证。

令牌颁发终结点

通过 OAuth WRAP 协议发出的所有 ACS 令牌请求都定向到 ACS 令牌颁发终结点。 此终结点的 URI 取决于访问控制命名空间。 命名空间在令牌请求 URI 中显示为 DNS 名称前缀。 与路径一样,DNS 名称的剩余部分是固定的。 例如,如果要从名为“mysnservice”的访问控制命名空间请求令牌,可以将令牌请求定向到以下 URI: https://mysnservice.accesscontrol.windows.net/WRAPv0.9

密码令牌请求

使用密码令牌请求,客户端可以通过 OAuth WRAP 协议将用户名和密码直接从服务标识发送到 ACS 进行身份验证。 这是使用 OAuth WRAP 协议从 ACS 请求令牌的最简单方法。 除了建立 SSL 连接以外,此方法不需要任何加密功能。 在实践中,这类似于 REST Web 服务中常见的用户名/密码模型。 这种类型的令牌请求实际上是 HTTPS 格式的 POST。 密码令牌请求中的参数已经过格式编码。

以下是向名为“mysnservice”的命名空间发出的纯文本请求线路跟踪示例。

POST /WRAPv0.9/ HTTP/1.1
Host: mysnservice.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded

wrap_scope=http%3A%2F%2Fmysnservice.com%2Fservices%2F&
wrap_name=mysncustomer1&
wrap_password=5znwNTZDYC39dqhFOTDtnaikd1hiuRa4XaAj3Y9kJhQ%3D

下表提供了要在密码令牌请求中提供的参数的名称、说明和值要求:

参数名称 说明 值要求

wrap_scope

根据一组规则匹配令牌请求。 请将此参数的值设置为信赖方应用程序领域的值。 可以通过 ACS 管理门户在 “领域 ”字段中 (获取此值) ,方法是从 “信赖方应用程序 ”页中选择相应的信赖方应用程序。

  • HTTP 或 HTTP(s) URI

  • 无查询参数或定位点。

  • 路径段 <= 32。

  • 最大长度:256 个字符。

  • 必须采用 URL 编码。

wrap_name

验证下一参数的键。 将此参数的值设置为访问控制命名空间中的服务标识的名称。 可以通过 ACS 管理门户在 “名称 ”字段中 (获取此值) ,方法是从 “服务 标识”页中选择相应的服务标识。

  • 最小长度:1 个字符。

  • 最大长度:128 个字符。

  • 必须采用 URL 编码。

wrap_password

对传入的请求进行身份验证。 将此参数的值设置为访问控制命名空间中的服务标识的密码。 可以通过 ACS 管理门户在“编辑凭据) ”页上的“密码”字段中 (获取此值,方法是先在“服务标识”页上选择相应的服务标识,然后在“编辑服务标识”页上的“凭据”表中选择相应的密码。

  • 字符串,最小和最大长度分别为 1 和 64 个字符。

  • 必须采用 URL 编码。

将请求发送到 ACS 之前,必须对这些参数的值进行 URL 编码。 Web 应用程序或服务可以为客户端提供 wrap_scope 的值,或者客户端可以决定将 wrap_scope 参数的值设置为 Web 应用程序或服务资源目标的 URI。

通过 OAuth WRAP 协议的密码令牌请求还可以包含可在输出声明计算过程中使用的其他参数。 这些附加参数的名称和值必须采用 URL 编码,并且值必须用引号括住。

使用 执行的密码令牌请求方法相当直接。

WebClient client = new WebClient();
client.BaseAddress = string.Format("https://mysnservice.accesscontrol.windows.net");

NameValueCollection values = new NameValueCollection();
values.Add("wrap_name", "mysncustomer1");
values.Add("wrap_password", "5znwNTZDYC39dqhFOTDtnaikd1hiuRa4XaAj3Y9kJhQ=");
values.Add("wrap_scope", "http://mysnservice.com/services");

// WebClient takes care of the URL Encoding
byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

// the raw response from ACS
string response = Encoding.UTF8.GetString(responseBytes);

有关如何从 ACS 解压缩输出令牌并将其发送到 Web 应用程序或服务的信息,请参阅取消打包并将令牌发送到 Web 应用程序或服务。

SWT 令牌请求

还可以使用由对称密钥签名的 SWT 令牌通过 OAuth WRAP 协议从 ACS 请求令牌。 所有 SWT 令牌请求都将通过 HTTPS 格式的 POST 发出。 此令牌请求方法中的参数值已经过格式编码。

以下是向“mysnservice”命名空间发出的 SWT 令牌请求线路跟踪示例。

POST /WRAPv0.9/ HTTP/1.1
Host: mysnservice.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded

wrap_scope=http%3A%2F%2Fmysnservice.com%2Fservices%2F&
wrap_assertion_format=SWT&
wrap_assertion=Issuer%3dmysncustomer1%26HMACSHA256%3db%252f%252bJFwbngGdufECFjQb8qhb9YH0e32Cf9ABMDZFiPPA%253d

SWT 令牌请求必须具有以下参数和值:

参数名称 说明 值要求

wrap_scope

根据一组规则匹配令牌请求。

  • 请将此参数的值设置为信赖方应用程序领域的值。 可以通过 ACS 管理门户在 “领域 ”字段中 (获取此值) ,方法是从 “信赖方应用程序 ”页中选择相应的信赖方应用程序。

  • HTTP 或 HTTP(s) URI。

  • 无查询参数或定位点。

  • 路径段 <= 32。

  • 最大长度:256 个字符。

  • 必须采用 URL 编码。

wrap_assertion

这是要发送到 ACS 的输入令牌。

  • 具有包含颁发者和 HMACSHA256 参数的输入声明的已签名 SWT 令牌。

  • 最大长度:2048 个字符。

  • 必须采用 URL 编码。

wrap_assertion_format

这是要发送到 ACS 的输入令牌的格式。

SWT

如下面的示例中所示,发出 SWT 令牌请求所需的代码类似于发出密码令牌请求所需的代码。

WebClient client = new WebClient();
client.BaseAddress = string.Format("https://mysnservice.accesscontrol.windows.net");

NameValueCollection values = new NameValueCollection();
// add the wrap_scope
values.Add("wrap_scope", "http://mysnservice.com/services");
// add the format
values.Add("wrap_assertion_format", "SWT");
// add the SWT
values.Add("wrap_assertion", "Issuer=mysncustomer1&HMACSHA256=b%2f%2bJFwbngGdufECFjQb8qhb9YH0e32Cf9ABMDZFiPPA%3d");
// WebClient takes care of the remaining URL Encoding
byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

// the raw response from ACS
string response = Encoding.UTF8.GetString(responseBytes);

有关如何从 ACS 解压缩响应并将其发送到 Web 应用程序或服务的信息,请参阅取消打包并将令牌发送到 Web 应用程序或服务。

创建 SWT 令牌

SWT 令牌是使用颁发者密钥(对称密钥)签名的一组键/值对。 发送到 SWT 令牌请求中的 ACS 的 SWT 令牌必须包含 颁发者和HMACSHA256 参数,以及其他参数,例如 ,ExpiresOnAudience 和其他特定于客户端的声明。 下表提供了 SWT 令牌参数的名称和说明:

参数名称 说明

颁发者

在 ACS 中,查找用于对令牌进行签名的密钥。 如果该签名有效,则此值将用于执行输出声明计算。

可以将此参数设置为访问控制命名空间中标识提供者领域的值或访问控制命名空间中的服务标识的名称。 可以通过 ACS 管理门户在“编辑标识提供者”页上的“领域”字段中 (获取此值,) 方法是在“标识提供者”页上选择相应的标识提供者。 或者,可以通过 ACS 管理服务获取此值 – 这是为每个标识提供者创建的“颁发者”记录的名称属性。

HMACSHA256

在 ACS 中,验证 SWT 签名,并查找颁发者参数中命名的 颁发者 密钥。

SWT 签名是使用附加到服务标识的对称签名密钥或访问控制命名空间中的标识提供者创建的。

受众

如果存在,ACS 将使用此值来确保 ACS 是 SWT 令牌的预期目标。 这是访问控制命名空间的 URL,例如https://contoso.accesscontrol.windows.net/

ExpiresOn

如果存在(采用 Epoch 时间),则指示令牌是否已过期。 例如,此参数的值可以是 1324300962

其他声明

如果存在,ACS 使用这些参数来执行输出声明计算。 每个声明类型只能出现一次。 同一声明类型的多个声明值必须与以“,”(逗号)字符连接在一起。 有关在 ACS 中断言声明的详细信息,请参阅通过 OAuth WRAP 协议的声明断言。

下面的代码示例演示了如何使用 生成 SWT 令牌。 它包含一个类型,该类型生成包含 IssuerHMACSHA256 参数的 SWT 令牌。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;

public class TokenFactory
{
    string signingKey;   
    string issuer;
    
    public TokenFactory(string issuer, string signingKey)
    {
        this.issuer = issuer;
        this.signingKey = signingKey;
    }

    public string CreateToken()
    {
        StringBuilder builder = new StringBuilder();

        // add the issuer name
        builder.Append("Issuer=");
        builder.Append(HttpUtility.UrlEncode(this.issuer));

        string signature = this.GenerateSignature(builder.ToString(), this.signingKey);
        builder.Append("&HMACSHA256=");
        builder.Append(signature);

        return builder.ToString();
    }

   
    private string GenerateSignature(string unsignedToken, string signingKey)
    {
        HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(signingKey));

        byte[] locallyGeneratedSignatureInBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(unsignedToken));

        string locallyGeneratedSignature = HttpUtility.UrlEncode(Convert.ToBase64String(locallyGeneratedSignatureInBytes));

        return locallyGeneratedSignature;
    }
}

SAML 令牌请求

SAML 令牌请求方法主要用于 AD FS 2.0 集成,并允许客户端使用企业标识 (Active Directory) 进行身份验证。 使用 SAML 令牌请求方法,可以通过 OAuth WRAP 协议将已签名的 SAML 1.1 或 AD FS 2.0 颁发的 SAML 2. (0 令牌发送到 ACS) 。

ACS 使用其规则来计算输出声明,将声明分组到 SWT 令牌(输出令牌)中,对其进行签名,然后通过 OAuth WRAP 协议将其返回到客户端。

SAML 令牌请求必须具有以下参数和值:

参数名称 说明 值要求

wrap_scope

根据一组规则匹配令牌请求。

  • 请将此参数的值设置为信赖方应用程序领域的值。 可以通过 ACS 管理门户在 “领域 ”字段中 (获取此值) ,方法是从 “信赖方应用程序 ”页中选择适当的信赖方应用程序。

  • HTTP 或 HTTP(s) URI。

  • 无查询参数或定位点。

  • 路径段 <= 32。

  • 最大长度:256 个字符。

  • 必须采用 URL 编码。

wrap_assertion

这是要发送到 ACS 的输入令牌。

  • 具有输入声明的已签名 SAML 1.1 或 2.0 令牌。 作为令牌限制,SAML 1.1 令牌至少需要一个输入声明。 这意味着,必须将标识提供程序或启用声明的服务标识用于 SAML 1.1 令牌身份验证。 SAML 2.0 令牌不需要使用任何输入声明(隐式 NameIdentifier 声明除外)针对服务标识进行身份验证,因此可以使用 SAML 2.0 令牌针对未启用声明的普通服务标识进行身份验证。

  • 必须采用 URL 编码。

wrap_assertion_format

这是要发送到 ACS 的输入令牌的格式。

SAML

以下是发出 SAML 令牌请求所需的代码示例。

private static string SendSAMLTokenToACS(string samlToken)
{
 try
 {
  WebClient client = new WebClient();
  client.BaseAddress = string.Format("https://mysnservice.accesscontrol.windows.net");
  NameValueCollection parameters = new NameValueCollection();
  parameters.Add("wrap_assertion_format", "SAML");
  parameters.Add("wrap_assertion", samlToken);
  parameters.Add("wrap_scope", "http://mysnservice.com/services");

  byte[] responseBytes = client.UploadValues("WRAPv0.9", parameters);
  string response = Encoding.UTF8.GetString(responseBytes);

  return response
   .Split('&')
   .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
   .Split('=')[1];
 }
 catch (WebException wex)
 {
  string value = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
  throw;
 }
}  

有关如何从 ACS 解压缩响应并将其发送到 Web 应用程序或服务的信息,请参阅取消包装令牌并将其发送到 Web 应用程序或服务。

通过 OAuth WRAP 协议声明断言

若要启用与 ACS 1.0 令牌请求行为的向后兼容性,ACS 支持在令牌请求过程中断言声明的功能。

将断言应用程序或服务注册为 ACS 标识提供程序。

执行此操作的建议方法是将断言应用程序或服务注册为 ACS 标识提供程序。 然后,应用程序或服务通过提供包含要断言的声明的 SAML 或 SWT 令牌来请求 ACS 中的令牌,并且此令牌使用 ACS 中存储的标识提供者密钥进行签名。 例如,可以通过 AD FS 2.0 或任何自定义安全令牌服务 (STS) (使用 Windows Identity Foundation (WIF) 在 ACS 中注册为WS-Federation标识提供者)通过 OAuth WRAP 协议将具有断言声明的 SAML 令牌请求发送到 ACS。

可以使用 ACS 管理门户通过WS-Federation元数据注册标识提供者,也可以使用 ACS 管理服务单独设置标识提供者属性、地址和密钥。 (例如,请参阅如何:使用 ACS 管理服务将 AD FS 2.0 配置为Enterprise标识提供者.) 这种在令牌请求中断言声明的方法不需要服务标识。 此方法通过所有 ACS 支持的协议正常运行。

解包令牌并将其发送到 Web 应用程序或服务

如果令牌请求已成功进行身份验证,ACS 将返回两个格式编码的参数: wrap_tokenwrap_token_expires_in。 这些参数的值分别是客户端可用来访问 Web 应用程序或服务的实际 SWT 令牌,以及此令牌的近似剩余生存期(以秒为单位)。

在将 SWT 令牌发送到 Web 应用程序或服务之前,客户端必须从 ACS 响应中提取和对它进行 URL 解码。 如果 Web 应用程序或服务要求在 HTTP Authorization 标头中提供令牌,该令牌的前面必须带有方案 WRAPv0.9

下面的代码示例演示了如何解包令牌和设置 Authorization 标头的格式。

WebClient client = new WebClient();
client.BaseAddress = string.Format("https://mysnservice.accesscontrol.windows.net");

NameValueCollection values = new NameValueCollection();
values.Add("wrap_name", "mysncustomer1");
values.Add("wrap_password", "5znwNTZDYC39dqhFOTDtnaikd1hiuRa4XaAj3Y9kJhQ=");
values.Add("wrap_scope", "http://mysnservice.com/services");

// WebClient takes care of the URL Encoding
byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

// the raw response from ACS
string response = Encoding.UTF8.GetString(responseBytes);

string token = response
    .Split('&')
    .Single(value => value.StartsWith("wrap_token=", StringComparison.OrdinalIgnoreCase))
    .Split('=')[1];

string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(token));

ACS 错误代码和描述

ACS 在无法满足令牌请求时返回错误。 错误中包含与 REST 设计相一致的 HTTP 响应代码。 在许多情况下,ACS 错误还包含一个 SubCode 并提供 Detail 有关失败情况的上下文。 错误格式为:Error:Code:<httpStatus>:Sub-Code:code>:D etail:<<message>。 错误的 Content-Type 始终是 text/plain。

HTTP/1.1 401 Access Forbidden
Content-Type: text/plain; charset=us-ascii

Error:Code:401:SubCode:T0:Detail:ACS50009: SWT token is invalid. :TraceID:<trace id value>:TimeStamp:<timestamp value>

有关 ACS 错误代码的详细信息,请参阅 ACS 错误代码

从 ACS 返回的错误进行调试或恢复时,通常需要读取响应正文。 下面的代码示例演示如何从 WebException 对象读取错误消息。

try
{
    WebClient client = new WebClient();
    client.BaseAddress = string.Format("https://mysnservice.accesscontrol.windows.net");

    NameValueCollection values = new NameValueCollection();
    values.Add("wrap_name", "mysncustomer1");
    values.Add("wrap_password", "5znwNTZDYC39dqhFOTDtnaikd1hiuRa4XaAj3Y9kJhQ=");
    values.Add("wrap_scope", "http://mysnservice.com/services");

    // WebClient takes care of the URL Encoding
    byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

    // the raw response from ACS
    string response = Encoding.UTF8.GetString(responseBytes);

    string token = response
        .Split('&')
        .Single(value => value.StartsWith("wrap_access_token=",       StringComparison.OrdinalIgnoreCase))
        .Split('=')[1];
}
catch (WebException wex)
{
    if (wex.Response != null)
    {
        // the response Stream contains the error message
        StreamReader reader = new StreamReader(wex.Response.GetResponseStream());
        string message = reader.ReadToEnd();
    }
    // Throw as appropriate
}

另请参阅

概念

ACS 操作指南