WebApi 2 在使用自定义 JWT 令牌进行授权时总是返回 401

Posted

技术标签:

【中文标题】WebApi 2 在使用自定义 JWT 令牌进行授权时总是返回 401【英文标题】:WebApi 2 always returning 401 on authorization using custom JWT token 【发布时间】:2019-01-03 13:13:53 【问题描述】:

我已经按照这个教程Create a RESTful API with authentication using Web API and Jwt

我设法让身份验证部分正常工作,但授权部分不工作(接近教程结束时)。如果我在授权标头中添加带有单词 bearer 的 jwt 令牌,它会给我 401 授权被拒绝。

我在想也许我需要创建一个自定义授权属性。

    有没有办法使用现有的授权属性? 现有的 Authorize 属性寻找什么来授权用户(不包括授权属性中的角色或用户参数)?

Startup.Auth.cs

public partial class Startup

    public static OAuthAuthorizationServerOptions OAuthOptions  get; private set; 

    public static string PublicClientId  get; private set; 

    // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    
        var issuer = ConfigurationManager.AppSettings["Issuer"];
        var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["Secret"]);

        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
        
            AuthenticationMode = AuthenticationMode.Active,
            AllowedAudiences = new[]  "Any" ,
            IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
            
                new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
            
        );

        app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
        
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/Token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat(issuer)
        );
    

CustomOAuthProvider

public class CustomOAuthProvider : OAuthAuthorizationServerProvider

    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[]  "*" );

        var blSecurity = new BLSecurity();
        var user = blSecurity.LogonUser(context.UserName, context.Password);

        if (!(user.ResponseType == Global.Response.ResponseTypes.Success))
        
            context.SetError("Authentication Error", "The user name or password is incorrect");
            return Task.FromResult<object>(null);
        

        var ticket = new AuthenticationTicket(SetClaimsIdentity(context, user.LoggedOnUser), new AuthenticationProperties());
        context.Validated(ticket);

        return Task.FromResult<object>(null);
    

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    
        context.Validated();
        return Task.FromResult<object>(null);
    

    private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, User user)
    
        //Add User Claims
        var identity = new ClaimsIdentity("JWT");
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("bn", user.BranchName));
        identity.AddClaim(new Claim("fn", user.FirstName));
        identity.AddClaim(new Claim("ln", user.LastName));

        //Add User Role Claims
        var blRole = new BLRole();
        var roles = blRole.GetRolesByUserId(user.UserID);

        if (roles != null && roles.Count > 0)
        
            foreach (var role in roles)
            
                identity.AddClaim(new Claim(ClaimTypes.Role, role.RoleName));
            
        

        return identity;
    

CustomJwtFormat

public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>

    private static readonly byte[] _secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
    private readonly string _issuer;

    public CustomJwtFormat(string issuer)
    
        _issuer = issuer;
    

    public string Protect(AuthenticationTicket data)
    
        if (data == null)
        
            throw new ArgumentNullException(nameof(data));
        

        var signingKey = new HmacSigningCredentials(_secret);
        var issued = data.Properties.IssuedUtc;
        var expires = data.Properties.ExpiresUtc;

        return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime.ToLocalTime(), expires.Value.UtcDateTime.ToLocalTime(), signingKey));
    

    public AuthenticationTicket Unprotect(string protectedText)
    
        throw new NotImplementedException();
    

【问题讨论】:

if i add the jwt token with the word bearer - 不要忘记 Bearer 和 token 之间的空间。喜欢Bearer mytokendata。你检查了你的令牌here吗? @tym32167 我在承载和令牌之间使用了一个空格,是的,我已经检查了 jwt.io 上的令牌及其有效 顺便说一句。你读过那个教程中的cmets吗?其他人也遇到了问题,似乎在 cmets 中有解决方案。 我忘了提到我更改了身份验证位以查看现有用户表而不是 aspnetusers 表我现在将显示代码 您是否尝试过那篇文章中 cmets 中推荐的内容?例如new JwtSecurityToken(_issuer, null, data.Identity.Claims,... -> new JwtSecurityToken(_issuer, “Any”, data.Identity.Claims,... 【参考方案1】:

我设法通过应用一个人在 cmets 部分发布的内容来解决它。见下文引用。

罗曼史拉姆科 2016 年 6 月 23 日 上面的代码有一个错误。 JwtBearerAuthenticationOptions 配置为

AllowedAudiences = new[] “任何” ,

但实际上,令牌内容不包含任何受众,因此您的请求会被拒绝。

解决这个问题的最快方法(不是最好的方法)是更改在 CustomJwtFormat 类的 Protect 方法中创建令牌的方式

new JwtSecurityToken(_issuer,null,data.Identity.Claims,issued.Value.UtcDateTime,expires.Value.UtcDateTime,signingKey);

到这个

new JwtSecurityToken(_issuer, “Any”, data.Identity.Claims,issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);

即传递“Any”而不是 null 作为第二个构造函数参数。

【讨论】:

以上是关于WebApi 2 在使用自定义 JWT 令牌进行授权时总是返回 401的主要内容,如果未能解决你的问题,请参考以下文章

如何对 ASP.NET WebApi 的每个请求应用自定义验证到 JWT 令牌?

Web API 验证来自自定义身份验证提供程序的 JWT 不记名令牌

如何使 AzureAD 和自定义 JWT 令牌在 Web API 中并行工作?

如何自定义 ASP .NET Web API JWT 令牌响应?

使用 Web API 对 JWT 令牌进行单元测试

Webapi 2.0如何在访问令牌过期时实现刷新JWT令牌