如何验证 Azure AD 安全令牌?

Posted

技术标签:

【中文标题】如何验证 Azure AD 安全令牌?【英文标题】:How to validate Azure AD security token? 【发布时间】:2017-02-13 11:00:24 【问题描述】:

下面的代码给了我Azure AD security token,我需要验证令牌是否有效。如何做到这一点?

// Get OAuth token using client credentials 
string tenantName = "mytest.onmicrosoft.com";
string authString = "https://login.microsoftonline.com/" + tenantName;

AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);

// Config for OAuth client credentials  
string clientId = "fffff33-6666-4888-a4tt-fbttt44444";
string key = "123v47o=";
ClientCredential clientCred = new ClientCredential(clientId, key);
string resource = "http://mytest.westus.cloudapp.azure.com";
string token;

Task<AuthenticationResult> authenticationResult = authenticationContext.AcquireTokenAsync(resource, clientCred);
token = authenticationResult.Result.AccessToken;
Console.WriteLine(token);
// How can I validate this token inside my service?                

【问题讨论】:

【参考方案1】:

验证令牌有两个步骤。首先,验证令牌的签名以确保令牌是由 Azure Active Directory 颁发的。其次,根据业务逻辑验证令牌中的声明。

例如,如果您正在开发单租户应用程序,我们需要验证 issaud 声明。并且您还需要验证nbf 以确保令牌没有过期。更多理赔可以参考here。

以下描述来自here关于签名验证的细节。 (注意:下面的示例使用 Azure AD v2 端点。您应该使用与客户端应用正在使用的端点对应的端点。)

来自 Azure AD 的访问令牌是一个 JSON Web 令牌 (JWT),由安全令牌服务以私钥签名。

JWT 包括 3 个部分:标头、数据和签名。从技术上讲,我们可以使用公钥来验证访问令牌。

第一步——检索和缓存签名令牌(公钥)

端点:https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

然后我们可以使用JwtSecurityTokenHandler 来验证令牌,使用下面的示例代码:

 public JwtSecurityToken Validate(string token)
 
     string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";

     ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);

     OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;

     TokenValidationParameters validationParameters = new TokenValidationParameters
     
         ValidateAudience = false,
         ValidateIssuer = false,
         IssuerSigningTokens = config.SigningTokens,
         ValidateLifetime = false
     ;

     JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();

     SecurityToken jwt;

     var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);

     return jwt as JwtSecurityToken;
 

如果您在项目中使用 OWIN 组件,验证令牌会更容易。我们可以使用下面的代码来验证令牌:

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
            );

然后我们可以使用下面的代码来验证令牌中的“范围”:

public IEnumerable<TodoItem> Get()

    // user_impersonation is the default permission exposed by applications in AAD
    if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
    
        throw new HttpResponseException(new HttpResponseMessage 
          StatusCode = HttpStatusCode.Unauthorized,
          ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found"
        );
    
    ...

这是一个使用 Azure AD 保护 Web API 的代码示例:

Protect a Web API using Bearer tokens from Azure AD

【讨论】:

我对令牌的验证有点困惑。假设我正在经营一家资产交易所。我有一个用户在下午 12 点获得了安全令牌。下午 1 点,我发现他们违反了交换规则,我进入 AAD 门户并阻止了该用户的登录。我是否了解该令牌将不再起作用需要一两天的时间?有没有更直接的方法会禁用该用户? 目前无法撤销Azure AD已经颁发的访问令牌。但是,我们可以通过在 Azure 门户上启用 访问应用程序所需的用户分配 功能并禁用用户来完美地禁用用户登录应用程序。如果您希望 Azure AD 支持撤销访问令牌,您可以从here 提交反馈。 @FeiXue 在有人复制粘贴您的示例并认为可以使用之前,请不要关闭示例代码中的重要验证规则(ValidateAudienceValidateIssuerValidateLifetime)生产,因为它有效。 我很好奇 - 谁以及如何管理公钥的缓存以及在哪些情况下更新缓存? @Patrick 确实,它已更改为 SigningKeys,另请参阅 ***.com/questions/45500752/…【参考方案2】:

只是想为使用 .net Core 2.0 的人添加 Fei 的答案

您必须修改Validate(string token) 方法的两行。

 var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
        stsDiscoveryEndpoint,
        new OpenIdConnectConfigurationRetriever()); //1. need the 'new OpenIdConnect...'

 OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
 TokenValidationParameters validationParameters = new TokenValidationParameters
 
     //decode the JWT to see what these values should be
     ValidAudience = "some audience",
     ValidIssuer = "some issuer",

     ValidateAudience = true,
     ValidateIssuer = true,
     IssuerSigningKeys = config.SigningKeys, //2. .NET Core equivalent is "IssuerSigningKeys" and "SigningKeys"
     ValidateLifetime = true
 ;

【讨论】:

请不要发布禁用三个最重要验证规则的答案。此外,您可能不应该自己在生产中使用它。 好点,我把它从“假”改成了“真”——我只是偏离了原来的答案 嗨@StevenLiekens,你能说明一下它在生产中的情况或链接以获得进一步的洞察力吗? @Sergiosolorzano 我最初有ValidateXXX = false(因为我从标记的答案中复制并粘贴了它),我已经将它更正为ValidateXXX = true,这就是史蒂文所指的(它应该在生产中设置为“true”) 这也是它现在在 .net Framework 4.7.2 中的工作方式。 Azure 提供未签名令牌的密钥。【参考方案3】:

但是,如果您在项目中没有使用 OWIN,那会有点困难,或者至少很耗时。. 这篇文章Here 是很好的资源。

因为我没有太多要补充的,除了详细的代码。这里有一些对你有用的东西:

 public async Task<ClaimsPrincipal> CreatePrincipleAsync()
    
        AzureActiveDirectoryToken azureToken = Token.FromJsonString<AzureActiveDirectoryToken>();
        var allParts = azureToken.IdToken.Split(".");
        var header = allParts[0];
        var payload = allParts[1];
        var idToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryIdToken>();

        allParts = azureToken.AccessToken.Split(".");
        header = allParts[0];
        payload = allParts[1];
        var signature = allParts[2];
        var accessToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryAccessToken>();

        var accessTokenHeader = header.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureTokenHeader>();
        var isValid = await ValidateToken(accessTokenHeader.kid, header, payload, signature);
        if (!isValid)
        
            throw new SecurityException("Token can not be validated");
        
        var principal = await CreatePrincipalAsync(accessToken, idToken);
        return principal;
    



    private async Task<bool> ValidateToken(string kid, string header, string payload, string signature)
    
        string keysAsString = null;
        const string microsoftKeysUrl = "https://login.microsoftonline.com/common/discovery/keys";

        using (var client = new HttpClient())
        
            keysAsString = await client.GetStringAsync(microsoftKeysUrl);
        
        var azureKeys = keysAsString.FromJsonString<MicrosoftConfigurationKeys>();
        var signatureKeyIdentifier = azureKeys.Keys.FirstOrDefault(key => key.kid.Equals(kid));
        if (signatureKeyIdentifier.IsNotNull())
        
            var signatureKey = signatureKeyIdentifier.x5c.First();
            var certificate = new X509Certificate2(signatureKey.ToBytesFromBase64URLString());
            var rsa = certificate.GetRSAPublicKey();
            var data = string.Format("0.1", header, payload).ToBytes();

            var isValidSignature = rsa.VerifyData(data, signature.ToBytesFromBase64URLString(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            return isValidSignature;
        

        return false;
    

我在这里使用的一些函数对你不可用,它们是自我描述的。

【讨论】:

我正在使用 MS OAuth 2 登录我的网站。我在 microsoftKeysUrl 中找不到我的密钥。我还有其他地方可以获得正确的密钥吗? 您是说您知道令牌中的密钥 id 或名称但找不到它login.microsoftonline.com/common/discovery/keys ???这很奇怪,应该在这里找到。然而问题是他们改变了签名算法的版本并引入了随机数,这将使您难以验证它。你点击哪个端点来获取令牌 V1 或 V2 ? 尝试了一整天后,我设法在这里找到了我的孩子。 login.microsoftonline.com/common/discovery/v2.0/keys。但是我必须启用 AD 中的隐式选项,否则密钥不会显示。 很高兴听到.. 所以是的,我看到您指定了获取密钥的版本。事情瞬息万变。

以上是关于如何验证 Azure AD 安全令牌?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何在后端验证来自 azure AD 的访问令牌?

如何使用 Python 在 Azure AD 中验证令牌

通过 Azure AD 验证不透明的访问令牌

如何使用身份服务器 3 和 microsoft 团队应用程序使用 Azure AD 令牌进行身份验证

在控制台应用程序中验证 Azure AD 不记名令牌

Azure AD - 为啥我无法验证 Azure AD 为我的 Web API 颁发的 JWT 令牌?收到“IDX10516:签名验证失败”错误