如何在asp.net core web api中实现JWT Refresh Tokens(没有第三方)?

DJD*_*JDJ 18 c# asp.net asp.net-web-api

我正在使用使用JWT的asp.net核心实现web api.我正在尝试学习,我没有使用像IdentityServer4这样的第三方解决方案.

我已经让JWT配置工作了,但我很难知道如何在JWT到期时实现刷新令牌.

下面是startup.cs中我的Configure方法中的一些示例代码.

app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
    AuthenticationScheme = "Jwt",
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = new TokenValidationParameters()
    {
        ValidAudience = Configuration["Tokens:Audience"],
        ValidIssuer = Configuration["Tokens:Issuer"],
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    }
});
Run Code Online (Sandbox Code Playgroud)

下面是用于生成JWT的Controller方法.为测试目的,我将到期时间设置为30秒.

    [Route("Token")]
    [HttpPost]
    public async Task<IActionResult> CreateToken([FromBody] CredentialViewModel model)
    {
        try
        {
            var user = await _userManager.FindByNameAsync(model.Username);

            if (user != null)
            {
                if (_hasher.VerifyHashedPassword(user, user.PasswordHash, model.Password) == PasswordVerificationResult.Success)
                {
                    var userClaims = await _userManager.GetClaimsAsync(user);

                    var claims = new[]
                    {
                        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                    }.Union(userClaims);

                    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
                    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

                    var token = new JwtSecurityToken(
                            issuer: _jwt.Issuer,
                            audience: _jwt.Audience,
                            claims: claims,
                            expires: DateTime.UtcNow.AddSeconds(30),
                            signingCredentials: creds
                        );

                    return Ok(new
                    {
                        access_token = new JwtSecurityTokenHandler().WriteToken(token),
                        expiration = token.ValidTo
                    });
                }
            }
        }
        catch (Exception)
        {

        }

        return BadRequest("Failed to generate token.");
    }
Run Code Online (Sandbox Code Playgroud)

非常感谢一些指导.

小智 18

首先,您需要生成刷新令牌并将其保留在某处.这是因为您希望能够在需要时使其无效.如果您遵循与访问令牌相同的模式 - 其中所有数据都包含在令牌中 - 最终落入坏人手中的令牌可用于在刷新令牌的生命周期内生成新的访问令牌,可能很长一段时间.

那么你需要坚持到底是什么?你需要一些不易猜测的特殊标识符,GUID就可以了.您还需要数据能够发出新的访问令牌,很可能是用户名.拥有用户名后,您可以跳过VerifyHashedPassword(...) - 部分但是对于其余部分,只需遵循相同的逻辑.

要获取刷新令牌,通常使用范围"offline_access",这是您在发出令牌请求时在模型(CredentialViewModel)中提供的内容.与普通访问令牌请求不同,现在您不需要提供用户名和密码,而是提供刷新令牌.使用刷新令牌获取请求时,您将查找持久标识符并为找到的用户发出令牌.

以下是快乐路径的伪代码:

[Route("Token")]
[HttpPost]
public async Task<IActionResult> CreateToken([FromBody] CredentialViewModel model)
{
    if (model.GrantType is "refresh_token")
    {
        // Lookup which user is tied to model.RefreshToken
        // Generate access token from the username (no password check required)
        // Return the token (access + expiration)
    }
    else if (model.GrantType is "password")
    {
        if (model.Scopes contains "offline_access")
        {
            // Generate access token
            // Generate refresh token (random GUID + model.username)
            // Persist refresh token
            // Return the complete token (access + refresh + expiration)
        }
        else
        {
            // Generate access token
            // Return the token (access + expiration)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)