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的主要内容,如果未能解决你的问题,请参考以下文章