简体   繁体   English

ASP.NET Core 5 JWT 身份验证失败,响应代码为 401

[英]ASP.NET Core 5 JWT Authentication fails with response code 401

I'm trying to implement JWT based authentication in my ASP.NET Core 5 Web API.我正在尝试在我的 ASP.NET Core 5 Web API 中实现基于 JWT 的身份验证。 However, I always end up with the response code 401 when using my APIs marked with the [Authorize] attribute.但是,在使用标有[Authorize]属性的 API 时,我总是以响应代码 401 结束。

Here's what I have so far.这是我到目前为止所拥有的。 First, my AccountController issues a JWT if the user provides a valid username and password:首先,如果用户提供有效的用户名和密码,我的AccountController发出 JWT:

[Authorize]
[ApiController]
[Route("api/" + Constants.ApiVersion + "/Accounts")]
public class AccountController : ControllerBase
{
  private readonly UserManager<AppUser>     _userManager;
  private readonly IPasswordHasher<AppUser> _passwordHasher;


  public AccountController(UserManager<AppUser> userManager, IPasswordHasher<AppUser> passwordHasher)
  {
    _userManager    = userManager;
    _passwordHasher = passwordHasher;
  }


  [AllowAnonymous]
  [HttpPost]
  [Route("Token")]
  public async Task<IActionResult> Login([FromForm]LoginBindingModel model)
  {
    if(model == null)
    {
      return BadRequest();
    }

    if(!ModelState.IsValid)
    {
      return BadRequest(ModelState);
    }

    AppUser user = await _userManager.FindByNameAsync(model.UserName);

    if(user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
    {
      return Unauthorized();
    }

    SymmetricSecurityKey    encryptionKey   = new(Encoding.UTF8.GetBytes("TODO_Find_better_key_and_store_as_secret"));
    JwtSecurityTokenHandler jwtTokenHandler = new();
    SecurityTokenDescriptor tokenDescriptor = new()
    {
      Subject            = new ClaimsIdentity(new[] { new Claim("UserName", user.UserName) }),
      Expires            = DateTime.UtcNow.AddDays(7),
      SigningCredentials = new SigningCredentials(encryptionKey, SecurityAlgorithms.HmacSha256Signature)
    };

    SecurityToken jwtToken = jwtTokenHandler.CreateToken(tokenDescriptor);
    string        token    = jwtTokenHandler.WriteToken(jwtToken);

    return Ok(token);
  }


  [HttpPost]
  [Route("ChangePassword")]
  public async Task<ActionResult> ChangePassword([FromBody]ChangePasswordBindingModel model)
  {
    if(model == null)
    {
      return BadRequest();
    }

    if(!ModelState.IsValid)
    {
      return BadRequest(ModelState);
    }

    AppUser user = await _userManager.GetUserAsync(User);

    if(user == null)
    {
      return new StatusCodeResult(StatusCodes.Status403Forbidden);
    }

    IdentityResult result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);

    return GetHttpResponse(result);
  }


  ...
}

This code seems to work as it should.这段代码似乎可以正常工作。 It returns a token that is successfully parsed by jwt.io and contains the username I put into it.它返回一个由jwt.io成功解析并包含我放入其中的用户名的令牌。

Next, the Startup class looks as follows:接下来,Startup 类如下所示:

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
  }


  public IConfiguration Configuration
  {
    get;
  }


  public void ConfigureServices(IServiceCollection services)
  {
    services.Configure<ApplicationSettings>(Configuration.GetSection(nameof(ApplicationSettings)));
    services.AddIdentityCore<AppUser>(options =>
    {
      Configuration.GetSection(nameof(IdentityOptions)).Bind(options);
    });
    services.AddScoped<IPasswordHasher<AppUser>, Identity.PasswordHasher<AppUser>>();
    services.AddTransient<IUserStore<AppUser>, UserStore>();
    services.AddAuthentication(options =>
    {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
      options.SaveToken = true;
      options.TokenValidationParameters = new TokenValidationParameters
      {
        ValidateIssuer           = true,
        ValidIssuer              = "whatever",
        ValidateAudience         = true,
        ValidAudience            = "whatever",
        ValidateIssuerSigningKey = true,
        IssuerSigningKey         = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("TODO_Find_better_key_and_store_as_secret"))
      };
    });
    services.AddMvc();
    services.AddControllers();
  }


  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
    if(env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }

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

I'm sending an HTTP POST request to the Token route which returns me the JWT.我正在向Token路由发送一个 HTTP POST 请求,该请求返回 JWT。 After that I'm sending an HTTP POST request with the necessary JSON data in the request body and Authorization: Bearer <the JWT> in the header to the ChangePassword route.之后,我将一个 HTTP POST 请求在请求正文中包含必要的 JSON 数据和Authorization: Bearer <the JWT>发送到ChangePassword路由。

However, that always returns me response code 401 without any additional information or exception.但是,它总是返回响应代码 401,没有任何附加信息或异常。

I'm unaware what the magic in Startup.ConfigureServices is actually supposed to do behind the scenes.我不知道Startup.ConfigureServices的魔法实际上应该在幕后做什么。 Anyway, it obviously doesn't work.无论如何,这显然行不通。 Does anyone know what is going on and what to do to make it work?有谁知道发生了什么以及如何使其工作?

However, that always returns me response code 401 without any additional information or exception.但是,它总是返回响应代码 401,没有任何附加信息或异常。

That is because you set ValidateIssuer and ValidateAudience true but there is no Issuer and Audience in the generated token.这是因为您将ValidateIssuerValidateAudience设置为 true 但生成的令牌中没有IssuerAudience

One way is that you can set Issuer and Audience in code:一种方法是您可以在代码中设置IssuerAudience

SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor()
{
    Issuer= "whatever",
    Audience= "whatever",
    Subject = new ClaimsIdentity(new[] { new Claim("UserName", user.Name) }),
    Expires = DateTime.UtcNow.AddDays(7),
    SigningCredentials = new SigningCredentials(encryptionKey, SecurityAlgorithms.HmacSha256Signature)
};

Another way is that you can set ValidateIssuer and ValidateAudience false:另一种方法是您可以将ValidateIssuerValidateAudience设置为 false:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,  //change here..
        ValidIssuer = "whatever",
        ValidateAudience = false,  //change here..
        ValidAudience = "whatever",
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("TODO_Find_better_key_and_store_as_secret"))
    };
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM