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

ASP.NET Core 7+Carter JWT授权问题及配置优化咨询

问题背景

我用ASP.NET Core 7结合Carter库搭建了一个极简API,路由配置如下:

public void AddRoutes(IEndpointRouteBuilder app)
{
     // 分组/api/user端点
     var group = app.MapGroup("/api/user/");

     // /api/user/id端点配置
     group.MapGet("{id}", GetUserById).WithName(nameof(GetUserById)).
         Produces(StatusCodes.Status200OK).
         Produces(StatusCodes.Status400BadRequest).
         Produces(StatusCodes.Status401Unauthorized).RequireAuthorization();
 }

GetUserById方法实现:

[Authorize(AuthenticationSchemes = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)]
public static async Task<Results<Ok<UserDataDTO>, BadRequest<string>>> GetUserById(int id, IUserLogic userLogic)
{
    var user = await userLogic.GetUserByIdAsync(id);

    if (user is null)
        return TypedResults.BadRequest("指定ID的用户不存在。");

    var userData = new UserDataDTO(user);

    return TypedResults.Ok(userData);
}

我已经配置了JWT认证服务、Swagger安全定义,Token生成逻辑如下:

public string GetJwtToken(User existingUser)
{
    var claims = new Claim[] {
        new(JwtRegisteredClaimNames.Sub,existingUser.UserId.ToString()),
        new(JwtRegisteredClaimNames.Email,existingUser.UserEmail),
        new(JwtRegisteredClaimNames.AuthTime, DateTime.UtcNow.ToString()),
    };

    var signingCredentials = new SigningCredentials(
        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfiguration.SecretKey)),
        SecurityAlgorithms.HmacSha256);

    var jwtToken = new JwtSecurityToken(
        _jwtConfiguration.Issuer,
        _jwtConfiguration.Audience,
        claims,
        DateTime.UtcNow.AddSeconds(-5),
        DateTime.UtcNow.AddDays(7),
        signingCredentials);

    return new JwtSecurityTokenHandler().WriteToken(jwtToken);
}

现在通过Swagger调用接口始终返回401 Unauthorized,需要解决以下问题:

  1. 解决接口返回401未授权的问题;
  2. 在API端点中读取并使用JWT中的数据;
  3. 移除/login、/register端点的Swagger锁图标(取消授权限制);
  4. 从appsetting.{Environment}.json中正确读取JWT配置数据;
  5. 在Swagger的授权字段中正确填入JWT令牌。

问题解答

1. 解决401未授权问题

从几个核心环节排查修复:

  • 匹配认证配置:确保AddJwtBearer的Issuer、Audience、SecretKey和生成Token时完全一致,重点检查密钥编码(必须UTF8)和签名算法(HmacSha256),示例配置:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidateAudience = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero // 关闭时钟偏移容错,避免时间差导致验证失败
        };
    });
  • 调整中间件顺序:在Program.cs中,UseAuthentication()必须在UseAuthorization()之前调用,且放在路由配置前:
app.UseAuthentication();
app.UseAuthorization();
// 然后配置Carter路由
app.MapCarter();
  • 规范Token格式:Swagger传入的Token必须带Bearer 前缀(比如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...),否则认证中间件无法识别。
  • 简化授权配置:方法上的[Authorize]和路由的RequireAuthorization()重复,建议去掉方法上的特性,只用路由的授权配置,避免Scheme冲突。

2. 读取并使用JWT中的数据

有两种常用方式:

方式1:直接从HttpContext提取

修改GetUserById方法,注入HttpContext即可获取Claims:

public static async Task<Results<Ok<UserDataDTO>, BadRequest<string>>> GetUserById(int id, IUserLogic userLogic, HttpContext context)
{
    // 获取Token中的用户ID(Sub声明)
    var userIdFromToken = context.User.FindFirstValue(JwtRegisteredClaimNames.Sub);
    // 获取用户邮箱
    var userEmail = context.User.FindFirstValue(JwtRegisteredClaimNames.Email);
    
    // 业务逻辑...
    var user = await userLogic.GetUserByIdAsync(id);
    if (user is null)
        return TypedResults.BadRequest("指定ID的用户不存在。");
    
    var userData = new UserDataDTO(user);
    return TypedResults.Ok(userData);
}

方式2:用自定义特性绑定声明

先创建绑定器和特性:

public class FromClaimBinder : IModelBinder
{
    private readonly string _claimType;

    public FromClaimBinder(string claimType) => _claimType = claimType;

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var claimValue = bindingContext.HttpContext.User.FindFirstValue(_claimType);
        if (claimValue == null)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }

        if (bindingContext.ModelType == typeof(int) && int.TryParse(claimValue, out var intValue))
        {
            bindingContext.Result = ModelBindingResult.Success(intValue);
        }
        else if (bindingContext.ModelType == typeof(string))
        {
            bindingContext.Result = ModelBindingResult.Success(claimValue);
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }
        return Task.CompletedTask;
    }
}

public class FromClaimAttribute : ModelBinderAttribute
{
    public FromClaimAttribute(string claimType) : base(typeof(FromClaimBinder)) => ClaimType = claimType;

    public string ClaimType { get; }

    public override IModelBinder GetBinder(ModelBinderProviderContext context) => new FromClaimBinder(ClaimType);
}

然后在方法中直接使用:

public static async Task<Results<Ok<UserDataDTO>, BadRequest<string>>> GetUserById(
    int id, 
    IUserLogic userLogic,
    [FromClaim(JwtRegisteredClaimNames.Sub)] int userIdFromToken,
    [FromClaim(JwtRegisteredClaimNames.Email)] string userEmail)
{
    // 直接使用绑定好的声明数据
    var user = await userLogic.GetUserByIdAsync(id);
    if (user is null)
        return TypedResults.BadRequest("指定ID的用户不存在。");
    
    var userData = new UserDataDTO(user);
    return TypedResults.Ok(userData);
}

3. 移除/login、/register的Swagger锁图标

给这些路由添加AllowAnonymous(),既允许匿名访问,又能让Swagger去掉锁图标:

public void AddRoutes(IEndpointRouteBuilder app)
{
    // 登录、注册路由,允许匿名
    app.MapPost("/api/login", Login).AllowAnonymous();
    app.MapPost("/api/register", Register).AllowAnonymous();

    // 用户组路由,保留授权要求
    var group = app.MapGroup("/api/user/");
    group.MapGet("{id}", GetUserById).WithName(nameof(GetUserById))
        .Produces(StatusCodes.Status200OK)
        .Produces(StatusCodes.Status400BadRequest)
        .Produces(StatusCodes.Status401Unauthorized)
        .RequireAuthorization();
}

如果Swagger仍显示锁图标,可自定义过滤器排除这些端点:

public class CustomSecurityFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var allowAnonymous = context.ApiDescription.ActionDescriptor.EndpointMetadata.Any(m => m is AllowAnonymousAttribute);
        if (allowAnonymous) return;

        operation.Security = new List<OpenApiSecurityRequirement>
        {
            new()
            {
                {
                    new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } },
                    Array.Empty<string>()
                }
            }
        };
    }
}
// 在Swagger配置中注册过滤器
builder.Services.AddSwaggerGen(c =>
{
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT格式:Bearer {token}",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    c.OperationFilter<CustomSecurityFilter>();
});

4. 从环境配置文件读取JWT数据

首先在appsettings.json(或对应环境的文件,如appsettings.Development.json)中添加配置:

{
  "Jwt": {
    "Issuer": "YourApiIssuer",
    "Audience": "YourApiAudience",
    "SecretKey": "YourStrongSecretKeyAtLeast16Chars",
    "ExpiresInDays": 7
  }
}

创建配置类绑定数据:

public class JwtConfig
{
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public string SecretKey { get; set; } = string.Empty;
    public int ExpiresInDays { get; set; }
}

在Program.cs中注册配置:

// 绑定配置并注入
var jwtConfig = builder.Configuration.GetSection("Jwt").Get<JwtConfig>()!;
builder.Services.AddSingleton(jwtConfig);

// 在Token生成类中注入使用
public class TokenGenerator(JwtConfig jwtConfig)
{
    public string GetJwtToken(User existingUser)
    {
        // 直接使用jwtConfig的属性生成Token
    }
}

5. Swagger中正确填入JWT令牌

操作步骤:

  1. 点击Swagger页面右上角的Authorize按钮;
  2. 在弹出的输入框中,填写Bearer 加上完整的Token字符串(比如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...);
  3. 点击Authorize确认,关闭对话框;
  4. 此时调用需要授权的接口,Swagger会自动在请求头中携带Authorization字段。

如果仍返回401,检查Token是否过期、签名密钥是否和配置一致、Issuer/Audience是否匹配。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 22:52:04