我在 ASP.NET Core MVC 中使用 JSON Web Token (JWT) 实现身份验证时遇到问题。我有一个带有登录端点的 ASP.NET Core API,该端点返回 JWT 以响应用户登录。以下是 API 中我的登录端点的代码:
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginUserDTO userDTO)
{
// ... (omission for brevity)
return Accepted(new TokenRequest { Token = await _authManager.CreateToken(), RefreshToken = await _authManager.CreateRefreshToken() });
}
public async Task<string> CreateToken()
{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
var token = GenerateTokenOptions(signingCredentials, claims);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List<Claim> claims)
{
var jwtSettings = _configuration.GetSection("Jwt");
var expiration = DateTime.Now.AddMinutes(Convert.ToDouble(
jwtSettings.GetSection("lifetime").Value));
var token = new JwtSecurityToken(
issuer: jwtSettings.GetSection("Issuer").Value,
claims: claims,
expires: expiration,
signingCredentials: signingCredentials
);
return token;
}
private async Task<List<Claim>> GetClaims()
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, _user.UserName)
};
var roles = await _userManager.GetRolesAsync(_user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
return claims;
}
private SigningCredentials GetSigningCredentials()
{
var key = _configuration.GetSection("Jwt:KEY").Value;
var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
}
现在,在我的 MVC 项目中,我有一个 Login 方法,该方法调用 API 进行登录并安全地保存令牌:
public async Task<IActionResult> Login(LoginViewModel model)
{
// ... (omission for brevity)
var token = await GetAccessToken(model); // call tha API to get the token
if (!string.IsNullOrEmpty(token))
{
SaveToken(token);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid username or password");
}
return View(model);
}
private void SaveTokenSecurely(string token)
{
// Puoi salvare il token in un cookie sicuro, ad esempio:
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddHours(1)
};
Response.Cookies.Append("AccessToken", token, cookieOptions);
}
以下是我在 MVC 项目中配置身份验证的方法:
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = jwtSettings.GetSection("Key").Value;
var issuer = jwtSettings.GetSection("Issuer").Value;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
};
});
API 和 MVC 项目的 appsettings.json 文件相同:
"Jwt": {
"Issuer": "MY.PROJECT.Api",
"lifetime": 15,
"Key": "SUPERR STRONG KEY"
}
问题是,尽管成功登录并获取了令牌,当我尝试使用 [Authorize(Roles = "Administrator")] 属性访问我的隐私页面时,我仍然收到 401 错误。我已验证“管理员”角色存在于我的令牌的有效负载中。
我在jwt.io和中检查了令牌。如果未选中
secret base64 encoded
,我会收到无效签名。
我还尝试从
Postman调用
Privacy()
设置 Authorization
> Bearer Token
> and then the token
,这按预期工作。
我知道 Stack Overflow 上有几个关于 ASP.NET Core MVC 中 JWT 令牌未经授权的错误的问题,我已经研究了其中的一些问题:
有人知道问题可能是什么或者我如何解决它吗?预先感谢您的帮助!
我解决了这个问题:
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = jwtSettings.GetSection("Key").Value;
var issuer = jwtSettings.GetSection("Issuer").Value;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Cookies["AccessToken"];
context.Token = token;
return Task.CompletedTask;
}
};
});
虽然这不是一个很好的解决方案。您能就我的问题提供完整的答案吗?
我认为@DerDingens(在评论中)是正确的,总结一下:
'Authorization: Bearer <JWT_TOKEN>'
因为这就是 JWT 的工作原理:
'JWT auth' -> 表示 'Authorization: Bearer ...' 标头必须存在
如果您使用cookie,那么:
“Cookie”身份验证 -> Cookie 必须存在
解决方案:在UseAuth..中间件之前添加设置标头“Authorization:Bearer”的中间件,例如:
// Add this middleware before authorization
// obviously 'AccessToken' is Your cookie name
app.Use(async (context, next) =>
{
var token = context.Request.Cookies["AccessToken"];
if (!string.IsNullOrEmpty(token) &&
!context.Request.Headers.ContainsKey("Authorization"))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
await next();
});
我没有更好的想法,希望这能有所帮助。
问候
NeuroXiq