ASP.NET Core JWT 身份验证总是返回 401 未经授权。

38

我正尝试在我的 asp.net core webAPI 上尽可能简单地实现 JWT 身份验证。我不知道我缺少了什么,但即使使用正确的 bearer token,它始终返回 401。

以下是我的 configureServices 代码:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(
               x =>
               {
                   x.RequireHttpsMetadata = false;
                   x.SaveToken = true;
                   x.TokenValidationParameters = new TokenValidationParameters
                   {
                       ValidateIssuerSigningKey = true,
                       IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("A_VERY_SECRET_SECURITY_KEY_FOR_JWT_AUTH")),
                       ValidateAudience = false,
                       ValidateIssuer = false,
                   };
               }
                );
            services.AddControllers();

            services.AddDbContext<dingdogdbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("dingdogdbContext")));
        }

这是我生成令牌的方式

        [AllowAnonymous]
        [HttpPost("/Login")]
        public ActionResult<User> Login(AuthModel auth)
        {
            var user = new User();
            user.Email = auth.Email;
            user.Password = auth.Password;
            //var user = await _context.User.SingleOrDefaultAsync(u=> u.Email == auth.Email && u.Password==auth.Password);
            //if(user==null) return NotFound("User not found with this creds");

            //starting token generation...
            var tokenHandler = new JwtSecurityTokenHandler();
            var seckey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("A_VERY_SECRET_SECURITY_KEY_FOR_JWT_AUTH"));
            var signingCreds = new SigningCredentials(seckey, SecurityAlgorithms.HmacSha256Signature);
            var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
            {
                Subject = new System.Security.Claims.ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, user.Id.ToString()) }),
                SigningCredentials = signingCreds,
                Expires = DateTime.UtcNow.AddDays(7),
            });
            user.Token = tokenHandler.WriteToken(token);
            return user;
        }

我在 app.useRouting() 后添加了 app.useAuthorization()。当我向 /Login 发送 POST 请求时,会收到令牌。但是,当我在 Postman 中使用该令牌查询任何其他端点时(将令牌添加到授权/JWT中),每次都会收到“401未经授权”的错误消息。我还有什么遗漏的吗?


1
你是怎么传递token的? 在你的StartUp.Configure里,你是否使用了app.UseAuthentication()? - Marius Steinbach
1
哦,谢谢。我只是在使用app.UseAuthorization()而没有使用app.UseAuthentication()。添加了它就可以了! - Vishal Ghosh
9个回答

100

请记住,在ASP框架中,UseAuthenticationUseRoutingUseAuthorization中间件必须正确配置,以便正确地将标识上下文注入到HTTP请求中。

看起来应该像这样:(.NET Core 3.1)

编辑:相同的代码适用于.NET 5和.NET 6

            app.UseAuthentication();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

1
应该在UseAuthorization()之前添加app.UseAuthentication(),以避免401错误的发生!请注意,这是程序中一个常见的错误。 - jeanie77
1
中间件顺序也解决了我的问题,我在 .NET Core 3.1 上也感谢它。 - mrbitzilla
1
完美,这就是解决方案。 - Alessandro Albi
1
@DerekWilliams 我不确定这是好事还是坏事 :P - Jakub Kozera
2
不确定为什么 .Net Core 文档会有不同的建议?https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0 它明确指出在 UseRouting 之后,UseEndpoints 之前。 - Robin1990
显示剩余5条评论

28

步骤1: 首先确保在startup.cs类中configure方法的顺序:

下面我给出了asp.net core 3.1的有效顺序表格。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();
        app.UseAuthentication();
        
        app.UseAuthorization();
       

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
如果第一步无效,请尝试第二步:确保令牌验证参数和令牌生成参数及算法相同。要做到这一点,请转到startup.cs类的ConfigureServices方法,以及生成令牌的类或方法(在我的情况下是UserService类)。 ConfigureServices方法代码:
public void ConfigureServices(IServiceCollection services)
    {
        var connectionString = Configuration.GetConnectionString("mySQLConnectionString");

        services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(connectionString));
        services.AddIdentity<IdentityUser, IdentityRole>(options =>
        {
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequiredLength = 5;
        }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

        services.AddAuthentication(auth =>
        {
            auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            
        }).AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidAudience = Configuration["AuthSettings:Audience"],
                ValidIssuer = Configuration["AuthSettings:Issuer"],
                RequireExpirationTime = true,
                IssuerSigningKey =
                    new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(Configuration["AuthSettings:key"])),
                ValidateIssuerSigningKey = true,

            };
        });
        services.AddScoped<IUserService, UserService>();
        services.AddControllers();
    }

令牌生成代码:

 public async Task<UserManagerResponse> LoginUserAsync(LoginVIewModel model)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if(user == null)
        {
            return new UserManagerResponse
            {
                Message = "There is no user with that email",
                iSSuccess= false
            };
        }
        var result = await _userManager.CheckPasswordAsync(user, model.Password);
        if(! result)
        {
            return new UserManagerResponse
            {
                Message = "Your Provided password not match eith our system ",
                iSSuccess = false
            };

        }

        var clims = new[]
        {
            new Claim("Email", model.Email),
            new Claim(ClaimTypes.NameIdentifier, user.Id)
        };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:key"]));
        var token = new JwtSecurityToken(
            issuer: _configuration["AuthSettings:Issuer"],
            audience: _configuration["AuthSettings:Audience"],
            claims: clims,
            expires: DateTime.Now.AddDays(30),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );
        string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);

        return new UserManagerResponse
        {
            Message = tokenAsString,
            iSSuccess = true,
            ExpireDate = token.ValidTo
        };
    }
}

请注意,我的情况是在appsetting.json中有一些拼写错误。例如,在令牌生成代码中,我称之为 "Audince",但在appSetting.json中它是 "Audience"。因此,两个"Audience"不匹配。

             audience: _configuration["AuthSettings:Audince"]

Appsetting.json 代码:

"AllowedHosts": "*",
  "AuthSettings": {
    "key": "TThis is mfw sec test token",
    "Audience": "www.mfw.com",
    "Issuer": "www.mfw.com"
  }

请点个赞吧。我浪费了很多天 :( - Hoque MD Zahidul
1
谢谢,Configure中的顺序确实很重要 :) - hurrii

2
首先,您需要检查使用configureServices代码生成的JWT令牌是否有效。要验证JWT令牌,您可以使用JWT调试器。它将解析JWT令牌值为每个参数,通过这些参数,您可以验证哪些参数值被错误地分配,并且JWT调试器还会提供JWT有效或无效的状态。一旦您找出了问题,您可以解决已识别的错误或采取下一步行动。

2
在为此问题苦苦挣扎了几个小时并尝试了多个问题的不同解决方法后,我终于找到了问题所在:
在你的appsettings.json文件中,打开Microsoft.AspNetCore.Authentication包的日志记录功能。
{
  "Logging": {
    "Console": {
      "LogLevel": {
        "Microsoft.Hosting.Lifetime": "Trace",
        "Microsoft.AspNetCore.Authentication": "Information"
      }
    }
  },
// other attributes
}

这将会将身份验证失败记录到Visual Studio服务器控制台。

enter image description here

在我的情况下,生成令牌时我没有将expires参数传递给new JwtSecurityToken构造函数。但是我在TokenValidationParameters构造函数中验证了令牌的生命周期。

0

总结

在我的情况下,我没有使用任何身份验证服务器,但我将主机作为有效发行者提供。 它验证了算法和密钥的Authority,但返回了空值,这导致系统抛出未处理的异常。 通过从AddJwtBearer(options => ...)中删除JwtBearerOptions中的options.Authority来解决此问题。

之后,我遇到了401错误,通过从AddJwtBearer(options => ...)中删除JwtBearerOptions中的options.Audience来解决它,还在TokenValidationParameters中添加了ValidateLifetime(您可以在第1部分中看到)。


代码

部分(1)JWT配置

在.NET 6中:

builder.services.AddAuthentication(options => 
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => 
 {
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters() 
    {
       ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey,
       IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)),
       ValidateIssuer = jwtSettings.ValidateIssuer,
       ValidIssuer = jwtSettings.ValidIssuer,
       ValidateAudience = jwtSettings.ValidateAudience,
       ValidAudience = jwtSettings.ValidAudience,
       RequireExpirationTime = jwtSettings.RequireExpirationTime,
       ValidateLifetime = jwtSettings.RequireExpirationTime,
       ClockSkew = TimeSpan.FromDays(1),
    };
});

额外

从应用程序设置中获取你的JWT设置,可以使用以下任一方式:

"JsonWebTokenKeys"

这是 configuration 部分的名称:

var jwtSettings = new JwtSettings();
Configuration.Bind("JsonWebTokenKeys", jwtSettings);
builder.services.AddSingleton(jwtSettings);

//PART (1) => JWT Configuration goes here
//..
//.. 

或者这样:

services.Configure<JwtSettings>(configuration.GetSection("JsonWebTokenKeys"));
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
   var jwtSettings = serviceProvider.GetRequiredService<IOptions<JwtSettings>>().Value;
   
   //PART (1) => JWT Configuration goes here
   //..
   //.. 
}
           

0

我一再碰到相同的问题。我发现这是因为在 .AddJwtBearer() 中的 TokenValidationParameters 必须是这样的:

options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidateAudience = true,
        ValidAudience = builder.Configuration["Jwt:Audience"],
    };

只有在那种情况下,我才让它工作。


0

如果有帮助的话。

确保在身份验证令牌之前添加"Bearer"。在Swagger的输入字段中,我只放置了令牌而没有加上"Bearer"文本,导致了相同的错误。


0

这里还有一些其他问题,您可能希望查看并进行改进。登录机制当前包含一个7天过期的令牌。这意味着暴露的令牌仍将允许攻击者在7天内访问和冒充用户。通常最好的做法是:

  • 登录用户并生成一个有效期为1小时的令牌
  • 为用户提供一个永久设备令牌来代表设备
  • 验证设备和令牌(即使已过期),并可能生成新的令牌。

这使用户能够“注销”所有会话,以防出现问题。特别是大多数身份验证提供程序(如Auth0)或授权提供程序(如Authress)都提供此类功能和更多功能。


0
我之前也遇到了同样的问题,一直找不到解决办法,直到一天结束的时候,我关闭了Visual Studio并关机。第二天重新启动系统后,Postman的身份验证神奇地开始工作了,而且没有做任何更改。所以,如果你遇到了困难,不妨试试这个方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接