如何验证Azure AD安全令牌?

Neo*_*Neo 20 c# oauth azure oauth-2.0 azure-active-directory

以下代码告诉我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?                
Run Code Online (Sandbox Code Playgroud)

Fei*_*SFT 28

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

例如,如果您正在开发单个租户应用,我们需要验证issaud声明.而且您还需要验证nbf以确保令牌未过期.您可以在此处参考更多声明.

下面的说明是从这里大约签名验证的细节.(注意:下面的示例使用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;
 }
Run Code Online (Sandbox Code Playgroud)

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

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
            });
Run Code Online (Sandbox Code Playgroud)

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

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"
        });
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

使用Azure AD中的承载令牌保护Web API

  • @FeiXue请不要在你的示例代码(`ValidateAudience`,`ValidateIssuer`,`ValidateLifetime`)中关闭重要的验证规则,然后才能复制粘贴你的样本并认为因为它有效而去生产是好的. (8认同)
  • 目前,Azure AD 已经颁发的访问令牌无法撤销。但是,我们可以通过在 Azure 门户上启用**访问应用程序所需的用户分配** 功能并禁用用户来完美地禁用应用程序的用户登录。如果希望 Azure AD 支持撤销访问令牌,可以从 [此处](https://feedback.azure.com/forums/34192--general-feedback) 提交反馈。 (2认同)
  • 我很好奇 - 谁以及如何管理公钥的缓存以及在什么情况下缓存会更新? (2认同)
  • @Patrick确实,它已更改为SigningKeys,另请参阅/sf/ask/3185052671/ (2认同)

Amb*_*ung 7

只是想为使用.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
 };
Run Code Online (Sandbox Code Playgroud)

  • 好点,我把它从“假”改为“真”——我刚刚离开了原来的答案 (6认同)
  • 请不要发布禁用三个最重要验证规则的答案。同样,您可能不应该自己在生产中使用它。 (2认同)

Ass*_*sil 5

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

因为除了详细的代码之外,我没有太多要添加的内容。以下是对您有用的内容:

 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;
    }
Run Code Online (Sandbox Code Playgroud)

我在这里使用的一些函数不适合您,它们是自我描述性的。