如果 JSON Web 令牌(JWT)过期,从 Web 服务抛出错误的标准方法?
Posted
技术标签:
【中文标题】如果 JSON Web 令牌(JWT)过期,从 Web 服务抛出错误的标准方法?【英文标题】:Standard way to throw error from web service if JSON Web Token (JWT) is expired? 【发布时间】:2021-05-03 05:44:18 【问题描述】:我正在编写针对 Azure 云运行的 C# 代码。我的应用程序是一个 ASP.NET Core Web 服务,它公开方法但没有 UI。
我的大多数(不是全部)方法都需要 JSON Web Token (JWT) 进行授权。我的Startup.cs
中有这个:
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
options.Authority = Configuration["AppSettings:IdentityAuthEndpoint"];
options.RequireHttpsMetadata = bool.Parse(Configuration["AppSettings:RequireHttps"]);
options.ApiName = "data";
options.EnableCaching = true;
);
如果 JWT 丢失或完全损坏,则会引发错误。但是如果 JWT 刚刚过期,上面的内容会让用户通过。
根据这个 Stack Overflow 答案,我可以检查 context.User.Claims
是否为空:https://***.com/a/62289801/4290962
这似乎是真的。但是是否有标准的最佳实践方法来检测这一点并抛出标准化错误?还是我需要编写自定义代码来检查声明?我当然可以这样做。我只是在想应该有一个更漂亮的解决方案。
提前致谢!
【问题讨论】:
【参考方案1】:我建议您为此编写一个中间件,这将使您更好地控制您想要实现的目标。正如您所说,寻找现有解决方案很好,但这是我在我的一个企业应用程序中使用的解决方案。
拦截令牌 - 在每个请求上使用此中间件
public class JwtMiddleware
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
_next = next;
public async Task Invoke(HttpContext context, IUserService userService)
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null) AttachUserToContext(context, userService, token);
await _next(context);
private void AttachUserToContext(HttpContext context, IUserService userService, string token)
try
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(AppConfigBuilder.Build().Security.JwtSecret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
context.Items["User"] = userService.GetUserById(userId);
catch
在启动时注册
app.UseMiddleware<JwtMiddleware>();
然后创建一个身份验证过滤器
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
public void OnAuthorization(AuthorizationFilterContext context)
var user = (User)context.HttpContext.Items["User"];
if (user == null)
context.Result = new JsonResult(new Response
IsSuccess = false,
ResponseStatus = ResponseStatus.ERROR,
Message="Request terminated. Unauthorized access to protected resource.",
Info=new List<string>()
"Verify the auth token sending through this request",
"Verify if your token is invalid or expired",
"Request for a new token by logging in again"
)
StatusCode = StatusCodes.Status401Unauthorized ;
这就是所有需要做的。现在要保护任何端点,只需像这样使用 [Authorize] 属性。这将验证您的令牌并发出适当的标准响应。
[HttpGet]
[Authorize]
public IActionResult Get()
//Boilerplate code
var response = userService.GetAllUsers();
return (response.IsSuccess) ? Ok(response) : BadRequest(response);
仅供参考:您可以在授权过滤器上定义自己的标准响应类。我使用的是 ExpressGlobalExceptionHandler 库。
这是我创建的 JWT 令牌生成逻辑
private string GenerateJwtToken(User user)
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(appConfig.Security.JwtSecret);
var tokenDescriptor = new SecurityTokenDescriptor
Subject = new ClaimsIdentity(new[] new Claim("id", user.Id.ToString()) ),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
;
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
【讨论】:
谢谢。实际上,这与我所拥有的相似。您能否准确解释您在代码中的哪个位置验证令牌未过期?是打电话给tokenHandler.ValidateToken
吗?
是的。 ValidateToken 是 Microsoft 的 JwtSecurityTokenHandler 中的一个 InBuilt 函数。它检查令牌验证、到期和签名,因此您无需担心手动编写验证
@ClausAppel 更具体地说,在创建 JWT 令牌期间,我正在设置到期时间。 validate 方法将在内部对其进行验证。我已经编辑了答案并添加了 JWT 创建方法供您参考
现在它抛出一个异常说:“签名验证失败。无法匹配密钥”。它如何知道如何验证签名?我是否需要告诉它在哪里可以找到预期的签名密钥(就像我在上面对option.Authority
所做的那样)?我该怎么做?
“签名验证失败。无法匹配密钥” - 此错误表示它无法验证令牌。当你生成一个 JWT 令牌时,你应该传递一个“JwtSecret”。那应该是126位。你能分享你的 JWT 生成和验证逻辑吗?我假设那里有问题。对我来说,我正在从 appsettings.json 加载 JWT 机密【参考方案2】:
根据这个答案,我最终做了以下事情:https://***.com/a/34423434/4290962
请注意,_rejectExpiredJwt
是一个可配置的布尔值。
public JwtSecurityToken ValidateToken(string tokenStr)
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(
new RSAParameters
Modulus = FromBase64Url(_modulus),
Exponent = FromBase64Url(_exponent)
);
var validationParameters = new TokenValidationParameters
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = _issuer,
ValidateLifetime = _rejectExpiredJwt,
IssuerSigningKey = new RsaSecurityKey(rsa)
;
var handler = new JwtSecurityTokenHandler();
handler.ValidateToken(tokenStr, validationParameters, out SecurityToken validatedSecurityToken);
var validatedJwt = validatedSecurityToken as JwtSecurityToken;
return validatedJwt ?? throw new UnauthorizedException(
$"Token is not a nameof(JwtSecurityToken) but a validatedSecurityToken.GetType().Name.");
static byte[] FromBase64Url(string base64Url)
string padded = base64Url.Length % 4 == 0
? base64Url
: base64Url + "====".Substring(base64Url.Length % 4);
string base64 = padded.Replace("_", "/")
.Replace("-", "+");
return Convert.FromBase64String(base64);
【讨论】:
以上是关于如果 JSON Web 令牌(JWT)过期,从 Web 服务抛出错误的标准方法?的主要内容,如果未能解决你的问题,请参考以下文章
使用 JWT(JSON Web 令牌)设置令牌到期的 RESTful API