Почему я получаю SecurityTokenSignatureKeyNotFoundException?

Когда я пытаюсь передать этот JWT (выданный мобильными службами Azure) в качестве токена HTTP-заголовка/авторизации/носителя:

Header:
{
    "alg": "HS256", 
    "typ": "JWT", 
    "kid": "0"
}
Claims:
{
    "ver": 2, 
    "aud": "Facebook", 
    "iss": "urn:microsoft:windows-azure:zumo", 
    "urn:microsoft:credentials": "pYK8b5...", 
    "exp": 1436730730, 
    "uid": "Facebook:10000xxxxxxxxxx"
}

В моем ASP.NET WEB API настроено:

const string issuer = "urn:microsoft:windows-azure:zumo";
byte[] mobileServicesSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:SecretKey"]);

app.UseJwtBearerAuthentication(
    new JwtBearerAuthenticationOptions
    {
      AuthenticationMode = AuthenticationMode.Active,
      AllowedAudiences = new[] { "Facebook" },
      IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
              {
                  new SymmetricKeyIssuerSecurityTokenProvider(issuer,  mobileServicesSecret)
              }
    });

Я получил:

В System.IdentityModel.Tokens.Jwt.dll возникло первое случайное исключение типа «System.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException».

Подозреваю, это из-за наличия "детского" свойства?

РЕДАКТИРОВАТЬ: используя этот https://github.com/Magenic/JWTvalidator/tree/master/JwtValidator/JwtValidator можно проверить JWT, так что в этом нет ничего плохого. Но я действительно хочу использовать OWIN/Katana.


person Magnus Johansson    schedule 13.06.2015    source источник
comment
Не могли бы вы преодолеть проблему? Я застрял с этим. Не могли бы вы предоставить код.   -  person Kunal B.    schedule 16.04.2016
comment
@КуналБ. нет, извините, я так и не нашел решения для использования Owin/Katana. Мне пришлось использовать класс JwtValidator в ссылке и использовать пользовательский атрибут аутентификации для его проверки. Microsoft делает то, что у них получается лучше всего; одна команда (команда Azure Mobiles Services) сидит по одну сторону забора, другая (команда ASP.NET) сидит по другую сторону, и они, вероятно, никогда не разговаривали друг с другом... :(   -  person Magnus Johansson    schedule 16.04.2016
comment
Через 2 дня я нашел вот что: markwalsh .io/development/2014/12/02/ это сработало для меня. Во время отладки я все еще получаю какое-то странное сообщение.   -  person Kunal B.    schedule 17.04.2016


Ответы (1)


Google предлагает следующее: Вызов конечной точки tokeninfo

Вместо того, чтобы писать собственный код для выполнения этих шагов проверки, мы настоятельно рекомендуем использовать клиентскую библиотеку Google API для вашей платформы или позвонить в нашу конечную точку проверки tokeninfo.

Чтобы проверить токен идентификатора с помощью конечной точки tokeninfo, сделайте HTTPS-запрос POST или GET к конечной точке и передайте токен идентификатора в параметре id_token. Например, чтобы проверить токен XYZ123, выполните следующий запрос GET:

CustomJwtHandler.cs

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Configuration;
using Newtonsoft.Json;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using Services.Models;
using System.Security.Claims;

namespace Services
{
    /// <summary>
    ///  This is an implementation of Google JWT verification that
    ///  demonstrates:
    ///    - JWT validation
    /// </summary>
    /// @author [email protected] (Kunal Bajpai)


    public class CustomJwtHandler : DelegatingHandler
    {
        private const string URL_GOOGLE_TOKEN_INFO = "https://www.googleapis.com/oauth2/v3/tokeninfo";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpStatusCode statusCode;
            string token;

            var authHeader = request.Headers.Authorization;
            if (authHeader == null)
            {
                // Missing authorization header
                return base.SendAsync(request, cancellationToken);
            }

            if (!TryRetrieveToken(request, out token))
            {
                return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            try
            {
                ValidateToken(token);
                return base.SendAsync(request, cancellationToken);
            }
            catch (SecurityTokenInvalidAudienceException)
            {
                statusCode = HttpStatusCode.Unauthorized;
            }
            catch (SecurityTokenValidationException)
            {
                statusCode = HttpStatusCode.Unauthorized;
            }
            catch (Exception)
            {
                statusCode = HttpStatusCode.InternalServerError;
            }

            return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
        }
        /// <summary>
        /// Validates JWT Token
        /// </summary>
        /// <param name="JwtToken"></param>
        private void ValidateToken(string JwtToken)
        {
            try
            {
                using (WebClient wc = new WebClient())
                {
                    TokenInfo tokenInfo = JsonConvert.DeserializeObject<TokenInfo>(wc.DownloadString(URL_GOOGLE_TOKEN_INFO + "?id_token=" + JwtToken));

                    ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(ExtractClaims(tokenInfo), tokenInfo.Issuer));

                    Thread.CurrentPrincipal = claimsPrincipal;
                    HttpContext.Current.User = claimsPrincipal;
                }
            }
            catch (WebException e)
            {
                HttpStatusCode statusCode = ((HttpWebResponse)e.Response).StatusCode;
                if (statusCode == HttpStatusCode.BadRequest)
                {
                    throw new SecurityTokenValidationException();
                }
                else
                {
                    throw new Exception();
                }
            }
        }

        /// <summary>
        /// Tries to retrieve Token
        /// </summary>
        /// <param name="request"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            IEnumerable<string> authorizationHeaders;

            if (!request.Headers.TryGetValues("Authorization", out authorizationHeaders) ||
            authorizationHeaders.Count() > 1)
            {
                return false;
            }

            var bearerToken = authorizationHeaders.ElementAt(0);
            token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
            return true;
        }

        private List<Claim> ExtractClaims(TokenInfo tokenInfo)
        {
            List<Claim> claims = new List<Claim> {
                new Claim(ClaimTypes.Name, tokenInfo.Name),
                new Claim(ClaimTypes.Email, tokenInfo.Email),
                new Claim(ClaimTypes.GivenName, tokenInfo.GivenName),
                new Claim(ClaimTypes.Surname, tokenInfo.FamilyName),
                new Claim(ApplicationUser.CLAIM_TYPE_LOCALE, tokenInfo.Locale),
                new Claim(ClaimTypes.NameIdentifier, tokenInfo.ProviderKey, ClaimValueTypes.String, tokenInfo.Issuer),
                new Claim(ApplicationUser.CLAIM_TYPE_EMAIL_CONFIRMED, tokenInfo.IsEmailVerifed.ToString(), ClaimValueTypes.Boolean)
            };

            return claims;
        }
    }
}

TokenInfo.cs

using Microsoft.AspNet.Identity.EntityFramework;
using Newtonsoft.Json;

namespace Services.Models
{
    public class TokenInfo
    {
        [JsonProperty("iss")]
        public string Issuer { get; set; }

        [JsonProperty("aud")]
        public string AudienceClientId { get; set; }

        [JsonProperty("sub")]
        public string ProviderKey { get; set; }

        [JsonProperty("email_verified")]
        public bool IsEmailVerifed { get; set; }

        [JsonProperty("azp")]
        public string AndroidClientId { get; set; }

        [JsonProperty("email")]
        public string Email { get; set; }

        [JsonProperty("iat")]
        public long IssuedAt { get; set; }

        [JsonProperty("exp")]
        public long ExpiresAt { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("picture")]
        public string Picture { get; set; }

        [JsonProperty("given_name")]
        public string GivenName { get; set; }

        [JsonProperty("family_name")]
        public string FamilyName { get; set; }

        [JsonProperty("locale")]
        public string Locale { get; set; }

        [JsonProperty("alg")]
        public string Algorithm { get; set; }

        [JsonProperty("kid")]
        public string kid { get; set; }

        public override bool Equals(object obj)
        {
            if (obj.GetType() != typeof(ApplicationUser))
            {
                return false;
            }

            ApplicationUser user = (ApplicationUser)obj;
            bool hasLogin = false;

            foreach (IdentityUserLogin login in user.Logins)
            {
                if (login.ProviderKey == ProviderKey)
                {
                    hasLogin = true;
                    break;
                }
            }
            if (!hasLogin) { return false; }

            if (user.FirstName != GivenName) { return false; }
            if (user.LastName != FamilyName) { return false; }
            if (user.Locale != Locale) { return false; }

            return base.Equals(obj);
        }
    }
}

WebApiConfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;

namespace Services
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            config.MessageHandlers.Add(new CustomJwtHandler());
        }
    }
}
person Kunal B.    schedule 18.05.2016
comment
Как вы думаете, если мы так часто звоним в службу Google для проверки токена? - person Redplane; 18.04.2017
comment
Ничего не произойдет. Вероятно, вы исчерпаете квоту. - person Kunal B.; 18.04.2017
comment
Вот почему я думаю о проверке токена Google на сервере, который я реализую. - person Redplane; 18.04.2017