如何让 [Authorize] 属性在 .NET Core 6 Web API 中起作用?

我是一个菜鸟,试图在 .NET Core 6 Web API 项目上以 simplest 可能的方式做 JWT,但我什至无法让它工作。

要求:您需要登录才能调用 GetProductList API。
(我正在项目附带的 Swagger 上对此进行测试)

仅供参考,我的登录控制器:(按预期工作)

    [HttpPost("login")]
    public async Task<ActionResult> Login(LoginDto request)
    {
        var user = GetUserFromRequest(request);

        if (user == null)
            return BadRequest("Invalid credentials.");

        string jwt = CreateJwtToken(user.Id.ToString());
        Response.Cookies.Append(COOKIE_JWT, jwt, _cookieOptions);

        return Ok();
    }

    [HttpGet("user")]
    public IActionResult GetUser() 
    {
        try
        {
            var jwt = Request.Cookies[COOKIE_JWT];
            var userId = VerifyJwtAndGetUserId(jwt);

            return Ok(GetUserById(userId));
        }
        catch(Exception ex)
        {
            return Unauthorized();
        }
    }

    public static string CreateJwtToken(string userId)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY));
        var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
        var token = new JwtSecurityToken(
            issuer: userId,
            expires: DateTime.Now.AddDays(365),
            signingCredentials: cred
        );
        var jwt = new JwtSecurityTokenHandler().WriteToken(token);
        return jwt;
    }

    public static string VerifyJwtAndGetUserId(string jwt)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        tokenHandler.ValidateToken(jwt, new TokenValidationParameters {
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY)),
            ValidateIssuerSigningKey = true,
            ValidateIssuer = false,
            ValidateAudience = false
        }, out SecurityToken validatedToken);

        string userId = validatedToken.Issuer;
        return userId;
    }

问题是,如何使 [Authorize] 属性起作用?

    [HttpGet("list")]
    //[Authorize]
    public async Task<ActionResult<List<Product>>> GetProductList()
    {
        return Ok(GetProducts());
    }

上面的工作,但添加 [Authorize] 属性会给出一个带有以下标题的 401:(虽然上面的 GetUser 很好)

 content-length: 0 
 date: Mon,13 Jun 2022 23:27:32 GMT 
 server: Kestrel 
 www-authenticate: Bearer 

这是我的 Program.cs 中的内容:(也许这是错误的?)

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options => {
    options.RequireHttpsMetadata = false;
    options.TokenValidationParameters = new TokenValidationParameters {  // similar to the one in controller
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY)),
        ValidateIssuerSigningKey = true,
        ValidateIssuer = false,
        ValidateAudience = false
    };
    options.Events = new JwtBearerEvents {  // https://spin.atomicobject.com/2020/07/25/net-core-jwt-cookie-authentication/
        OnMessageReceived = ctx => {
            ctx.Token = ctx.Request.Cookies["jwt"];
            return Task.CompletedTask;
        }
    };
  });

解决方案:

app.UseAuthentication(); 移到 app.UserAuthorization(); 上方。

stack overflow How to get [Authorize] attribute work in .NET Core 6 Web API?
原文答案

答案:

作者头像

概括

### 前端

根据 JWT Introduction 如下

每当用户想要访问受保护的路由或资源时,用户代理应该发送 JWT,通常在 Authorization 标头中使用 Bearer 模式。标头的内容应如下所示:

授权:承载 [token]

So you need to add a header Authorization: Bearer <token> in your request on frontEnd.
a simple example at frontEnd. In this example the yourApiUrl should be your list api route.

   const token = getCookieValue(`COOKIE_JWT`);
   fetch(yourApiUrl, {
            headers: {
              "Authorization": `Bearer ${token}`,
            }
          })

getCookieValue function

const getCookieValue(name) =>{
document.cookie.match('(^|;)s*' + name + 's*=s*([^;]+)')?.pop() || ''
}

then your request will have the authorization header with a Bearer prefix.
a picture from my browser's dev tool.
image

然后 [Authorize] 属性应该工作


后端

可能不是前端的问题,那么你可以查看后端的代码。
后端 .NET 6 WebApi 中有关生成或检查 JWT 令牌的一些代码示例

生成令牌函数
用于登录控制器时生成令牌

  public string GenerateToken(TokenModel tokenModel, int 
    expireMinutes = 30)
    {
        var issuer = this._configuration.GetValue<string> ("JwtSettings:Issuer");
        var signKey = this._configuration.GetValue<string> 
("JwtSettings:SignKey");

        // Configuring "Claims" to your JWT Token
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Iss, issuer),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID
            new Claim(JwtRegisteredClaimNames.Sub, tokenModel.EmployeeNo), // User.Identity.Name
            new Claim(JwtRegisteredClaimNames.NameId, tokenModel.EmployeeNo),
            new Claim(JwtRegisteredClaimNames.Name, tokenModel.EmployeeName),
        };

        for (int i = 0; i < tokenModel.Roles.Length; i++)
        {
            claims.Add(new Claim("roles", tokenModel.Roles[i]));
        }

        var userClaimsIdentity = new ClaimsIdentity(claims);

        // Create a SymmetricSecurityKey for JWT Token signatures
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey));

        // HmacSha256 MUST be larger than 128 bits, so the key can't be too short. At least 16 and more characters.
        var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        // Create SecurityTokenDescriptor
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = userClaimsIdentity,
            NotBefore = DateTime.Now, // Default is DateTime.Now
            IssuedAt = DateTime.Now, // Default is DateTime.Now
            Expires = DateTime.Now.AddMinutes(expireMinutes),
            SigningCredentials = signingCredentials
        };

        // Generate a JWT securityToken, than get the serialized Token result (string)
        var tokenHandler = new JwtSecurityTokenHandler();
        var securityToken = tokenHandler.CreateToken(tokenDescriptor);
        var serializeToken = tokenHandler.WriteToken(securityToken);

        return serializeToken;
    }

程序.cs
.NET6 WebApi 中关于检查 Jwt 令牌的 Program.cs 设置。
📌 [NOTICE]: the method builder.Services.AddAuthorization(); MUST below the builder.Services.AddAuthentication method, or will result error.

   builder.Services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //check the HTTP Header's Authorization has the JWT Bearer Token
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",

                RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",

                // varify Issuer
                ValidateIssuer = true,
                ValidIssuer = builder.Configuration.GetValue<string>("JwtSettings:Issuer"),

                // 📌 Important!!! audience need to be set to false
                // Because the default is true. 
                // So if you didn't set this prop when generate token, then the api will always return a check token error
                ValidateAudience = false,

                ValidateLifetime = true,

                ValidateIssuerSigningKey = true,
                IssuerSigningKey =
                new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtSettings:SignKey")))
            };
        });

    builder.Services.AddAuthorization();

Easy Test by thunder client of VsCode

  1. Use the thumder client VsCode 测试 api 路由。

2.使用swagger的登录控制器获取JWTToken。
3.然后将token粘贴到迅雷客户端,加上前缀 Bearer
一个简单的迅雷客户端图片 enter image description here