You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Asp.net Web Api中JWT认证下[Authorize]返回401错误求助

解决ASP.NET Web API JWT认证后401未授权问题

嘿,我完全懂这种卡了两天的挫败感——能成功拿到token和refresh token,但就是过不了[Authorize]的关卡,确实让人挠头。咱们对着你的代码一步步排查,找出问题所在:

1. 认证类型不匹配(最可能的原因)

在OAuthTokenProvider的GrantResourceOwnerCredentials方法里,你创建ClaimsIdentity时用的是自定义的"JWT"认证类型:

ClaimsIdentity oAuthIdentity = new ClaimsIdentity("JWT");

但JWT Bearer认证中间件默认只会识别**"Bearer"**类型的身份标识。这就导致中间件无法关联你的token和用户身份,直接返回401。

修复方案:
把认证类型改成OAuthDefaults.AuthenticationType(它的底层值就是"Bearer"),或者直接写"Bearer":

// 推荐使用框架常量,避免硬编码错误
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);

2. JWT签名与验证配置不一致

检查你的Config.AudienceId和Config.AudienceSecret在两个地方是否完全一致:

  • 生成token的JwtFormat.Protect方法
  • 验证token的ConfigureOAuthTokenConsumption方法

哪怕是大小写、多余空格或者Base64解码错误,都会导致中间件判定token无效。

验证技巧:
把生成的token复制到JWT解码工具里(本地工具就行),核对以下字段:

  • iss(签发者)是否等于你配置的http://localhost:49860
  • aud(受众)是否等于你的Config.AudienceId
  • 签名是否显示为“有效”

3. ClaimsIdentity缺少必要声明

你的ClaimsIdentity里只设置了认证类型,没有添加任何用户相关的声明(比如用户名、角色)。虽然理论上不是强制要求,但部分场景下中间件会因为身份标识没有可验证的信息而拒绝授权。

修复方案:
添加几个基础声明:

ClaimsIdentity oAuthIdentity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
// 添加用户名声明
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
// 可选:添加角色声明,方便后续基于角色的授权
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, "RegularUser"));

4. OWIN中间件顺序错误

你的Startup里先配置了token生成中间件,再配置token验证中间件:

ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);

OWIN中间件是按添加顺序执行的,这意味着带有[Authorize]的请求会先经过授权服务器中间件(它只处理/oauth/token路径),然后才到JWT验证中间件——此时授权逻辑已经触发,但身份还没被验证,自然返回401。

修复方案:
调换配置顺序,先让验证中间件处理所有请求:

ConfigureOAuthTokenConsumption(app);
ConfigureOAuthTokenGeneration(app);

5. (次要)RefreshTokenProvider的同步方法未实现

虽然这不是当前401的直接原因,但你的RefreshTokenProvider里同步的Create和Receive方法抛了NotImplementedException。如果后续有同步调用的场景,会导致错误。建议补充实现:

public void Create(AuthenticationTokenCreateContext context)
{
    CreateAsync(context).Wait();
}

public void Receive(AuthenticationTokenReceiveContext context)
{
    ReceiveAsync(context).Wait();
}

附上你的实现代码(优化后版本)

Startup.cs

public class Startup {
    public void Configuration(IAppBuilder app) {
        // 修正顺序:先配置token验证,再配置token生成
        ConfigureOAuthTokenConsumption(app);
        ConfigureOAuthTokenGeneration(app);
    }
    private void ConfigureOAuthTokenGeneration(IAppBuilder app) {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new OAuthTokenProvider(),
            RefreshTokenProvider = new RefreshTokenProvider(),
            AccessTokenFormat = new Provider.JwtFormat("http://localhost:49860")
        };
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
    }
    private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
        var issuer = "http://localhost:49860";
        string audienceId = Config.AudienceId;
        byte[] audienceSecret = TextEncodings.Base64Url.Decode(Config.AudienceSecret);
        app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { audienceId },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[] {
                    new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                }
            });
    }
}

OAuthTokenProvider.cs

public class OAuthTokenProvider : OAuthAuthorizationServerProvider {
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
        context.Validated();
        return Task.FromResult<object>(null);
    }
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) {
        var allowedOrigin = "*";
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
        if (context.UserName != context.Password) {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }
        var props = new AuthenticationProperties(new Dictionary<string, string> {
            { "as:client_id", "Kaushik Thanki" }
        });
        // 修正认证类型
        ClaimsIdentity oAuthIdentity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
        // 添加用户声明
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        var ticket = new AuthenticationTicket(oAuthIdentity, props);
        context.Validated(ticket);
    }
}

JwtFormat.cs

public class JwtFormat : ISecureDataFormat<AuthenticationTicket> {
    private readonly string _issuer = string.Empty;
    public JwtFormat(string issuer) {
        _issuer = issuer;
    }
    public string Protect(AuthenticationTicket data) {
        if (data == null) {
            throw new ArgumentNullException("data");
        }
        string audienceId = Config.AudienceId;
        string symmetricKeyAsBase64 = Config.AudienceSecret;
        var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
        var issued = data.Properties.IssuedUtc;
        var expires = data.Properties.ExpiresUtc;
        var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime);
        var handler = new JwtSecurityTokenHandler();
        var jwt = handler.WriteToken(token);
        return jwt;
    }
    public AuthenticationTicket Unprotect(string protectedText) {
        throw new NotImplementedException();
    }
}

RefreshTokenProvider.cs

public class RefreshTokenProvider : IAuthenticationTokenProvider {
    private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
    // 补充同步实现
    public void Create(AuthenticationTokenCreateContext context) {
        CreateAsync(context).Wait();
    }
    public async Task CreateAsync(AuthenticationTokenCreateContext context) {
        var guid = Guid.NewGuid().ToString();
        var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary) {
            IssuedUtc = context.Ticket.Properties.IssuedUtc,
            ExpiresUtc = DateTime.UtcNow.AddYears(1)
        };
        var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
        _refreshTokens.TryAdd(guid, refreshTokenTicket);
        context.SetToken(guid);
    }
    // 补充同步实现
    public void Receive(AuthenticationTokenReceiveContext context) {
        ReceiveAsync(context).Wait();
    }
    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) {
        AuthenticationTicket ticket;
        if (_refreshTokens.TryRemove(context.Token, out ticket)) {
            context.SetTicket(ticket);
        }
    }
}

内容的提问来源于stack exchange,提问作者Kaushik Thanki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:58:20