用于下载和缓存用于令牌验证的公共签名密钥的良好算法

时间:2016-10-25 18:57:09

标签: jwt access-token identityserver3 thinktecture-ident-server

在katana web api中,我使用:

appBuilder.UseIdentityServerBearerTokenAuthentication(
    new IdentityServerBearerTokenAuthenticationOptions
    {
        Authority = "https://...",
        ValidationMode = ValidationMode.Local,
        RequiredScopes = new[] { "..." },
    });

这似乎很好地找到了来自权威机构的公共签名密钥,并且(希望?)缓存它们等等。虽然我还没有尝试过,但我知道那里有ASP的等价物。 NET Core。

现在我需要做同样的事情,但不是在web api中间件中。所以我试图找到IdentityServer3.AccessTokenValidation.IdentityServerBearerTokenValidationMiddleware用来执行此操作的代码。我只能看到它调用了UseOAuthBearerAuthentication,它似乎在Microsoft.Owin.Security.OAuth中。我还没有能够找到与该签名相匹配的源代码版本。

在我看来,有人可能正在使用System.IdentityModel.Tokens.JwtSecurityTokenHandler并将一小段代码放入TokenValidationParameters的IssuerSigningKeyResolver中。这个漂亮的小片段是从元数据地址获取签名密钥。有谁知道那个代码是什么,或者有一个效果很好的部分?显然,我可以写它,但我讨厌重新发明轮子,加上我的未经测试。

2 个答案:

答案 0 :(得分:1)

答案 1 :(得分:0)

谢谢,至少特权。深入了解DiscoverydocumentIssuerSecurityTokenProvider课程,我找到ConfigurationManager<OpenIdConnectConfiguration>。使用它,我已经为OWIN中间件之外的访问令牌验证提出了以下帮助器类。

征求意见!

using Microsoft.IdentityModel.Protocols;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;

namespace WpfClient
{
    public class AccessTokenValidator
    {
        protected TokenValidationParameters _accessTokenValidationParameters;

        public AccessTokenValidator(string stsRoot)
        {
            stsRoot = stsRoot.TrimEnd('/');
            var discoveryEndpoint = stsRoot + "/.well-known/openid-configuration";
            var webHandler = new WebRequestHandler();
            var httpClient = new HttpClient(webHandler);
            var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(discoveryEndpoint, httpClient);
            _accessTokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = stsRoot,
                RequireSignedTokens = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKeyResolver = (string token, SecurityToken securityToken, SecurityKeyIdentifier keyIdentifier, TokenValidationParameters validationParameters) => {
                    var signingTokens = configurationManager.GetConfigurationAsync().Result.JsonWebKeySet.GetSigningTokens();
                    foreach (var signingToken in signingTokens)
                    {
                        foreach (var clause in keyIdentifier)
                        {
                            var key = signingToken.ResolveKeyIdentifierClause(clause);
                            if (key != null)
                            {
                                return key;
                            }
                        }
                    }
                    return null;
                },
                RequireExpirationTime = true,
                ValidateAudience = false, // See https://github.com/IdentityServer/IdentityServer3/issues/1365: "OAuth2 does not use the term 'audience' ... it instead uses the term 'scope' ... 'audience' and the 'aud' claim are JWT specific concepts."
                ValidateLifetime = true,
            };
        }

        public void ValidateAccessToken(string accessToken, IEnumerable<string> requiredScopes, IEnumerable<string> requiredRoles)
        {
            ClaimsPrincipal claimsPrincipal;
            SecurityToken securityToken;

            var handler = new JwtSecurityTokenHandler();
            claimsPrincipal = handler.ValidateToken(accessToken, _accessTokenValidationParameters, out securityToken);
            if (claimsPrincipal == null)
            {
                throw new NullReferenceException("ClaimsPrincipal object returned is null");
            }

            RequireClaims("scope", requiredScopes, claimsPrincipal);
            RequireClaims("role", requiredRoles, claimsPrincipal);
        }

        private static void RequireClaims(string type, IEnumerable<string> requiredValues, ClaimsPrincipal claimsPrincipal)
        {
            if (requiredValues != null)
            {
                var haveClaims = claimsPrincipal.FindAll(type);

                var missingRequiredValues = requiredValues.Where(s => !haveClaims.Any(c => c.Value.Equals(s, StringComparison.InvariantCultureIgnoreCase))).ToArray();
                if (missingRequiredValues.Any())
                {
                    var list = string.Join(", ", missingRequiredValues);
                    throw new InvalidDataException($"Missing required {type} claims: {list}");
                }
            }
        }
    }
}