【问题标题】:How to get [Authorize] attribute work in .NET Core 6 Web API?如何让 [Authorize] 属性在 .NET Core 6 Web API 中起作用?
【发布时间】:2022-08-22 22:38:00
【问题描述】:

我是菜鸟,想做智威汤逊最简单的.NET Core 6 Web API 项目的可能方式,但我什至无法让它工作。

要求:需要登录才能调用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 

这就是我的程序.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(); 上方。

  • 您的端点需要一个不记名令牌,您的请求需要添加一个授权不记名 HTTP 标头
  • issuer: userId <-- 这是非常不正确的。您将sub(主题)声明与issuer 声明混淆了。
  • 谢谢毛毛虫和戴。有什么方法可以快速解决这个问题,让它正常工作吗?我是 JWT 的菜鸟,示例代码真的很有帮助
  • @JeremyLakeman 谢谢!这似乎是我需要的,但它仍然不起作用(Swagger 中的相同 401 错误)

标签: c# asp.net-core jwt asp.net-core-webapi authorize-attribute


【解决方案1】:

概括

前端

根据JWT Introduction 如下

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

授权:Bearer [token]

因此,您需要在前端的请求中添加标头Authorization: Bearer &lt;token&gt;
前端的一个简单示例。在此示例中,yourApiUrl 应该是您的列表

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

获取CookieValue 函数

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

那么您的请求将具有授权标头

[Authorize] 属性应该可以工作

后端

后端 NET 6 的 WebAPI 上的一些代码示例

我的 GenerateToken 函数

  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

[注意]:方法builder.Services.AddAuthorization();必须builder.Services.AddAuthentication 方法下方,或 将导致错误。

   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
                ValidateAudience = false,

                ValidateLifetime = true,

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

    builder.Services.AddAuthorization();

通过 VsCode 的迅雷客户端轻松测试

使用the thumder client VsCode 测试api路由。
使用 swagger 的登录控制器获取 JWTToken。 然后将令牌粘贴到迅雷客户端,并添加前缀Bearer
一个简单的迅雷客户端图片

【讨论】:

  • 谢谢许廷焕!!这是否意味着我无法使用 Swagger 对其进行测试?我会尝试为它创建一个前端......
  • 不幸的是你不能大摇大摆地测试它我以为我通常使用vscode的迅雷客户端marketplace.visualstudio.com/…
  • 嗨@HsuTingHuan,非常感谢您的帮助!我没有尝试过迅雷,但我尝试使用 Postman 但仍然得到 401。我尝试了你的代码,但用 axios.get 替换了 fetch 并且仍然得到 401。我们得到了 cookie,有没有办法告诉 .NET 查看 cookie 而不必将其复制到请求标头?
  • 我认为自定义属性可以完成这项工作..您是否将issuer 更改为sub
  • @HsuTingHuan 我向上移动了app.UseAuthentication(),它成功了!太傻了哈哈。非常感谢您的详细回答。感谢您的示例代码,我已将 Issuer 更改为 Claim NameId!
猜你喜欢
  • 1970-01-01
  • 2016-09-10
  • 2020-10-28
  • 2021-03-25
  • 2018-05-16
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多