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

ASP.NET Core中JWT负载畸形及受保护路由401问题求助

ASP.NET Core JWT认证问题排查与解决

问题描述

  1. 生成的JWT负载随机出现畸形,仅少数情况能在jwt.io上正确解析;
  2. 即使是解析正常的有效JWT,访问受保护路由时仍返回401 Unauthorized。

已尝试多种教程及官方文档未解决,相关代码如下:

Program.cs

// ./Program.cs
using System.Text;
using BackendAPI.Hubs;
using BackendAPI.Models;
using BackendAPI.Services;
using DotNetEnv;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

Env.Load();


string CONNECTION_STRING = Environment.GetEnvironmentVariable("DEFAULT_CONNECTION")!;
string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!;
string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!;
string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!;

builder.Services.AddControllers();
builder.Services.AddDbContext<ScalpersParadiseContext>(options => options.UseNpgsql(CONNECTION_STRING));

builder.Services.AddControllers();
builder.Services.AddScoped<IReservationNotificationService, ReservationNotificationService>();

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowSignalR", policy =>
    {
        policy.WithOrigins("http://localhost:3000")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

builder.Services.AddSignalR();

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = validIssuer,
            ValidateAudience = true,
            ValidAudience = validAudience,
            ValidateLifetime = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(jwtToken)
            ),
            ValidateIssuerSigningKey = true
        };
    });
builder.Services.AddScoped<IAuthService, AuthService>();

var app = builder.Build();
app.UseRouting();
app.UseCors("AllowSignalR");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<ReservationHub>("/hubs/reservations");

app.Run();

AuthController.cs

// ./Controllers/AuthController.cs
using BackendAPI.Models;
using BackendAPI.Models.DataTransferObjects;
using BackendAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace BackendAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class AuthController(IAuthService authService) : ControllerBase
{
[HttpPost("register")]
public async Task<ActionResult<User>> Register(UserDto request)
{
var user = await authService.RegisterAsync(request);
if (user is null)
{
return BadRequest("User already exists.");
}
return Ok(user);
}

        [HttpPost("login")]
        public async Task<ActionResult<string>> Login(UserDto request)
        {
            var token = await authService.LoginAsync(request);
    
            if (token is null)
            {
                return BadRequest("Invalid username or password");
            }
    
            return Ok(token);
        }
    
        [HttpPost("refresh-token")]
        public async Task<ActionResult<TokenResponseDto>> RefreshToken(RefreshTokenRequestDto request)
        {
            var result = await authService.RefreshTokensAsync(request);
    
            if (result is null || result.AccessToken is null || result.RefreshToken is null)
            {
                return Unauthorized("Invalid Refresh Token.");
            }
    
            return Ok(result);
        }
    
    
        [Authorize]
        [HttpGet("pleasework")]
        public IActionResult AuthenticatedOnlyEndpoint()
        {
            return Ok("You are authenticated!");
        }
    
        [Authorize(Roles = "Admin")]
        [HttpGet("admin-only")]
        public IActionResult AdminOnlyEndpoint()
        {
            return Ok("You are an admin!");
        }
    }

}

AuthService.cs

// ./Services/AuthService.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using BackendAPI.Models;
using BackendAPI.Models.DataTransferObjects;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

namespace BackendAPI.Services
{
    public class AuthService(ScalpersParadiseContext context) : IAuthService
    {
        private readonly string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!;
        private readonly string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!;
        private readonly string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!;

        public async Task<User?> RegisterAsync(UserDto request)
        {
            if (await context.Users.AnyAsync(u => u.Username == request.Username))
            {
                return null;
            }

            var user = new User();
            var hashedPassword = new PasswordHasher<User>()
                .HashPassword(user, request.Password);


            user.UserId = Guid.NewGuid();
            user.Username = request.Username;
            user.Email = request.Email;
            user.PasswordHash = hashedPassword;
            user.CreatedAt = DateTime.UtcNow;
            user.Role = "Standard";

            context.Users.Add(user);
            await context.SaveChangesAsync();

            return user;
        }
        public async Task<TokenResponseDto?> LoginAsync(UserDto request)
        {
            var user = await context.Users.FirstOrDefaultAsync(u => u.Username == request.Username);
            if (user is null)
            {
                return null;
            }
            if (new PasswordHasher<User>().VerifyHashedPassword(user, user.PasswordHash, request.Password) == PasswordVerificationResult.Failed)
            {
                return null;
            }

            return await CreateTokenResponse(user);
        }

        private async Task<TokenResponseDto> CreateTokenResponse(User user)
        {
            return new TokenResponseDto
            {
                AccessToken = CreateToken(user),
                RefreshToken = await GenerateAndSaveRefreshTokenAsync(user),
            };
        }

        public async Task<TokenResponseDto?> RefreshTokensAsync(RefreshTokenRequestDto request)
        {
            var user = await ValidateRefreshTokenAsync(request.UserId, request.RefreshToken);
            if (user is null)
            {
                return null;
            }

            return await CreateTokenResponse(user);
        }

        public async Task<User?> ValidateRefreshTokenAsync(Guid userId, string refreshToken)
        {
            var user = await context.Users.FindAsync(userId);
            if (user is null || user.RefreshToken != refreshToken ||
            user.RefreshTokenExpiryTime <= DateTime.UtcNow)
            {
                return null;
            }

            return user;
        }



        private string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }

        private async Task<string> GenerateAndSaveRefreshTokenAsync(User user)
        {
            var refreshToken = GenerateRefreshToken();
            user.RefreshToken = refreshToken;
            user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7);
            await context.SaveChangesAsync();
            return refreshToken;
        }

        private string CreateToken(User user)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, user.Username),
                new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()),
                new Claim(ClaimTypes.Role, user.Role),
            };

            var key = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(jwtToken)
            );

            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512);

            var tokenDescriptor = new JwtSecurityToken(
                issuer: validIssuer,
                audience: validAudience,
                claims: claims,
                expires: DateTime.UtcNow.AddDays(1),
                signingCredentials: creds
            );

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

    }
}

问题排查与解决

问题1:JWT负载随机畸形

  1. 环境变量读取异常:检查JWT_TOKEN、VALID_ISSUER、VALID_AUDIENCE是否包含换行、空格等特殊字符,读取时添加Trim()处理:
    string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!.Trim();
    string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!.Trim();
    string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!.Trim();
    
  2. 密钥长度不符合要求:HmacSha512要求密钥至少64字节,检查JWT_TOKEN长度,不足则更换更长的随机字符串。

问题2:有效JWT访问受保护路由返回401

  1. 统一环境变量读取逻辑:当前Program.cs和AuthService重复读取环境变量,可能导致值不一致。建议在Program.cs中将变量注入服务,避免重复读取。
  2. 添加角色声明映射:在JWT Bearer配置中明确角色声明类型,确保角色验证生效:
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            // 原有参数...
            RoleClaimType = ClaimTypes.Role
        };
        // 新增日志排查验证失败原因
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                Console.WriteLine($"认证失败:{context.Exception.Message}");
                return Task.CompletedTask;
            }
        };
    });
    
  3. 检查请求头格式:确保请求时使用Authorization: Bearer <token>格式,无多余空格或字符。
  4. 验证Token生成参数一致性:确认生成JWT时的issuer、audience、密钥与认证配置完全一致,包括大小写和特殊字符。

额外建议

  • 在AuthService.CreateToken()中打印生成的Token内容,方便排查畸形问题;
  • 使用JwtSecurityTokenHandler.ValidateToken()手动验证Token,定位验证逻辑问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:43:08