如何在asp.net core web api中实现JWT刷新令牌(无第三方)?

21

我正在使用asp.net core实现一个web api,并且正在使用JWT。由于我想学习,因此我没有使用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
    }
});

以下是用于生成JWT的控制器方法。为了测试目的,我将过期时间设置为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.");
    }

希望能得到一些指导,非常感激。


你发布的代码仅涉及验证访问令牌,而非刷新令牌。你能分享一下生成访问令牌的代码吗? - naslund
当然,我已经添加了用于生成JWT的代码。 - DJDJ
2个回答

22

首先,您需要生成一个刷新令牌并将其持久化。这是因为您希望在必要时能够使其失效。如果按照访问令牌的相同模式 - 在令牌中包含所有数据 - 那么落入错误手中的令牌可以用于生成新的访问令牌,而这个刷新令牌可以存在很长时间。

那么你到底需要持久化什么呢?

你需要一些不易猜测的唯一标识符,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)
        }
    }
}

0

生成RefreshToken的方法与创建AccessToken相同,非常简单。 但是,正如naslund所说,除了AccessToken之外,您还应该将refreshToken持久化存储在数据库等地方,以便将其标记为无效。 此外,我建议使用角色"refresh"来标记您的refreshToken,以便允许其用于刷新终端点。

因此,在使用refreshToken生成新的令牌对时,需要按照以下步骤进行操作:

  1. 检查令牌是否仍然有效,并且角色为"refresh"(由授权属性处理)
  2. 检查令牌是否仍然活动(通过数据库检查或者您存储令牌的任何其他地方)
  3. 为会话创建新的令牌对
  4. 添加/更新DB中的RefreshToken条目,以保存您的新RefreshToken
  5. 将新的AccessToken和RefreshToken发送给用户应用程序

代码示例:

class AuthResponse {
    public string AccessToken { get;set; }
    public string RefreshToken {get;set; }
}


class LoginRequest {
    public string Username {get;set;}
    public string Password {get;set;}
}

class AuthenticationController {
    [Unauthorized]
    public ActionResult<AuthResponse> Login(loginRequest login){
        //Create TokenPair
    }
    [Authorized(Role = "refresh")]
    public ActionResult<AuthResponse> Refresh(){
        //Load RefreshToken from Request
        //Create TokenPair
    }
    [Authorized(Role = "refresh")]
    public ActionResult<bool> Logout(){
        //Mark Token as invalid
        return true;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接