Web API在JWT上提供401

时间:2015-12-21 22:35:04

标签: c# asp.net asp.net-web-api jwt

我有以下问题:

web api使用JWT授权人员。我一直在关注本教程:here

令牌提供商工作正常,如邮递员图片所示:

enter image description here

但是当我尝试将邮递员中的令牌传递给以下控制器时:

    [Authorize]
    [Route("ChangePassword")]
    public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model) {
        if (!ModelState.IsValid) {
            return BadRequest(ModelState);
        }

        IdentityResult result = await this.AppUserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);

        if (!result.Succeeded)
            return GetErrorResult(result);

        return Ok();
    }

然后这将是结果:

enter image description here

我无法看到问题所在。我在启动文件中也是最后启动API。

public class Startup {

    public void Configuration(IAppBuilder app) {
        HttpConfiguration httpConfig = new HttpConfiguration();

        ConfigureOAuthTokenGeneration(app);
        ConfigureOAuthTokenConsumption(app);


        ConfigureWebApi(httpConfig);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.UseWebApi(httpConfig);
    }

    private void ConfigureWebApi(HttpConfiguration httpConfig) {
        httpConfig.MapHttpAttributeRoutes();
        var jsonFormatter = httpConfig.Formatters.OfType<JsonMediaTypeFormatter>().First();

        jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    }

    private void ConfigureOAuthTokenGeneration(IAppBuilder app) {
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() {

            //Set to false in production
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat("http://localhost:44300")
        };

        app.UseOAuthAuthorizationServer(OAuthServerOptions);
    }

    private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
        var issuer = "http://localhost:44300";
        var audienceId = "414e1927a3884f68abc79f7283837fd1";
        var audienceSecret = TextEncodings.Base64Url.Decode("qMCdFDQuF23RV1Y-1Gq9L3cF3VmuFwVbam4fMTdAfpo");

        // Api controllers with an [Authorize] attribute will be validated with JWT
        app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { audienceId },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                {
                    new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                }
            });
    }
}

2 个答案:

答案 0 :(得分:2)

我最近经历了相同的教程并遇到了类似的问题。所有具有[Authorize]属性的端点都返回401.我完全拆开了JwtBearerAuthentication中间件,发现了JWTSecurityTokenHandler确定观众是否有效的问题。

对于初学者,正如大多数指南所告诉您的那样,验证您的受众,发行人和秘密与您生成JWT令牌的位置以及ConfigureOAuthConsumption的位置相同。我发现在JWT创建方面很容易混淆这些。如果它们都正确,请查看下面的代码。

我最终创建了自己的JWT Handler,它来自JwtSecurityTokenHandler。它主要只调用基本方法,但它确实可以让您深入了解验证过程的工作原理。请注意ValidateToken中的代码更改。

   class CustomJWTTokenHandler : JwtSecurityTokenHandler
{
    public CustomJWTTokenHandler():base()
    {

    }
    public override bool CanReadToken(string tokenString)
    {
       var rtn =  base.CanReadToken(tokenString);
        return rtn;
    }
    public override bool CanValidateToken
    {
        get
        {
            return base.CanValidateToken;
        }
    }

    protected override ClaimsIdentity CreateClaimsIdentity(JwtSecurityToken jwt, string issuer, TokenValidationParameters validationParameters)
    {
        return base.CreateClaimsIdentity(jwt, issuer, validationParameters);
    }
    public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
    {
        try
        {
            var rtn = base.ValidateToken(token);
            return rtn;
        }
        catch (Exception)
        {

            throw;
        }
    }

    public override ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        var jwt = this.ValidateSignature(securityToken, validationParameters);
        if (validationParameters.ValidateAudience)
        {
            if (validationParameters.AudienceValidator != null)
            {
                if (!validationParameters.AudienceValidator(jwt.Audiences, jwt, validationParameters))
                {
                    throw new SecurityTokenInvalidAudienceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10231, jwt.ToString()));
                }
            }
            else
            {
                base.ValidateAudience(validationParameters.ValidAudiences, jwt, validationParameters);
            }
        }

        string issuer = jwt.Issuer;
        if (validationParameters.ValidateIssuer)
        {
            if (validationParameters.IssuerValidator != null)
            {
                issuer = validationParameters.IssuerValidator(issuer, jwt, validationParameters);
            }
            else
            {
                issuer = ValidateIssuer(issuer, jwt, validationParameters);
            }
        }

        if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jwt.Actor))
        {
            SecurityToken actor = null;
            ValidateToken(jwt.Actor, validationParameters, out actor);
        }

        ClaimsIdentity identity = this.CreateClaimsIdentity(jwt, issuer, validationParameters);
        if (validationParameters.SaveSigninToken)
        {
            identity.BootstrapContext = new BootstrapContext(securityToken);
        }

        validatedToken = jwt;
        return new ClaimsPrincipal(identity);
    }

    protected override JwtSecurityToken ValidateSignature(string token, TokenValidationParameters validationParameters)
    {
        var rtn =  base.ValidateSignature(token, validationParameters);
        var issuer = rtn.Issuer;

        return rtn;
    }
    protected override void ValidateAudience(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
    {
        if (audiences !=null && audiences.Any())
        {
            var jwt = securityToken as JwtSecurityToken;
            if (!jwt.Audiences.Any())
            {
                throw new Exception("token has no audiences defined");
            }
            var inBothList= audiences.Where(X => jwt.Audiences.Contains(X)).ToList();
            if (!inBothList.Any()){
                throw new Exception("token not in audience list");
            }

        }
        //base.ValidateAudience(audiences, securityToken, validationParameters);
    }


    public override SecurityToken ReadToken(string tokenString)
    {
        var rtnToken =  base.ReadToken(tokenString);
        //var validations = this.ValidateToken(rtnToken);
        return rtnToken;
    }
}

当您设置UseJwtBearerAuthentication中间件时,此处理程序已连线:

            app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
                AllowedAudiences = new List<string>() { JWTConfigs.audience },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                {
                    new CustomSymmetricKeyIssuerSecurityTokenProvider(JWTConfigs.issuer, key)
                },
                TokenHandler = new CustomJWTTokenHandler()
            }
        );

希望这对你有用,或者至少指出你的令牌失败的原因。

答案 1 :(得分:0)

我想我已经找到了这两个教程(thisthis)的问题。请查看CustomJwtFormatProtect()方法

public string Protect(AuthenticationTicket data) {
   ...
   var issued = data.Properties.IssuedUtc;
   ...
   var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims,
       issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
   ...
}

看起来JwtSecurityToken期待 local 中的notBefore参数,而不是UTC时区。如果作者在约旦(UTC + 2),这将意味着notBefore将比现在早2小时,但它仍然有效。但是,由于我在加利福尼亚州(UTC-8),notBefore设置为8小时以后,令牌验证失败!解决方案是

DateTimeOffset issued = data.Properties.IssuedUtc?.ToLocalTime();

采用新的C#6格式,或

DateTimeOffset issued = data.Properties.IssuedUtc.Value.ToLocalTime()

使用经典语法。感谢@Treetopvt推动我通过中间件进行调试。使用此更改标准JwtSecurityTokenHandler可以正常工作