Intro
最近在多个项目中都有用到 Jwt Token 认证,就想着把之前项目里 Jwt Token 的使用封装一下,以便于之后集成起来更加地方便,不用再拷贝代码了.
JWT
JWT 是 JSON Web Token 的缩写,是目前最流行的基于 Token 的认证解决方案,JWT 是一种无状态的认证方式,我们不需要依赖 Session,更为简单,对于随时准备扩容缩容的云原生应用来说更加的友好。
JWT token 的格式分成三个部分,他们之间以 “.” 作为分隔
-
Header(头部) -
Payload(负载) -
Signature(签名)
我们可以在 https://jwt.io 网站上查看 token 的内容,也可以自己写个小工具来查看,token 的内容是 base64 URL 编码的
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符
+
、/
和=
,在 URL 里面有特殊含义,所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。
JWT Token 内容解析之后如下所示:
Header 是 token 所用到的签名算法信息,默认是 HMAC SHA 256(HS256) 以及 RSA SHA 256(RS256)
Payload 就是我们 token 中保存的信息,这些信息是可以解析成明文的,所以内容上不能保存敏感信息,只能保存一些敏感度不高的信息
Signature 是基于 header 和 payload 生成出来的,例如使用 HMAC SHA 256 签名
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Sample
介绍了一些基本知识,我们再来看示例吧
首先需要集成 WeihanLi.Web.Extensions
这个 NuGet 包
我们只需要注册服务即可:
services.AddJwtTokenService(options =>
{
options.SecretKey = Guid.NewGuid().ToString();
options.Issuer = "https://id.weihanli.xyz";
options.Audience = "SparkTodo";
});
SecretKey
是用来生成 token 和 token 签名校验的,默认签名方式 是 HMAC SHA256
Issuer
是签发人,是指定谁颁发的 token
Audience
是受众,是指 token 是给谁用的
注册好服务之后,我们就可以从依赖注入服务中获取 ITokenService
来进行 token 的操作了
[HttpGet("getToken")]
public async Task<IActionResult> GetToken([Required] string userName, [FromServices] ITokenService tokenService)
{
var token = await tokenService
.GenerateToken(new Claim("name", userName));
return token.WrapResult().GetRestResult();
}
[HttpGet("validateToken")]
public async Task<IActionResult> ValidateToken(string token, [FromServices] ITokenService tokenService)
{
return await tokenService
.ValidateToken(token)
.ContinueWith(r =>
r.Result.WrapResult().GetRestResult()
);
}
GetToken:
VaidateToken:
Implement
我们来看一些实现细节
public class JwtTokenService : ITokenService
{
private readonly JwtSecurityTokenHandler _tokenHandler = new();
private readonly JwtTokenOptions _tokenOptions;
private readonly Lazy<TokenValidationParameters>
_lazyTokenValidationParameters;
public JwtTokenService(IOptions<JwtTokenOptions> tokenOptions)
{
_tokenOptions = tokenOptions.Value;
_lazyTokenValidationParameters = new(() =>
_tokenOptions.GetTokenValidationParameters());
}
public virtual Task<TokenEntity> GenerateToken(params Claim[] claims)
=> GenerateTokenInternal(claims);
public virtual Task<TokenValidationResult> ValidateToken(string token)
{
return _tokenHandler.ValidateTokenAsync(token, _lazyTokenValidationParameters.Value);
}
private async Task<TokenEntity> GenerateTokenInternal(bool refreshToken, Claim[] claims)
{
var now = DateTimeOffset.UtcNow;
var claimList = new List<Claim>()
{
new (JwtRegisteredClaimNames.Iat, now.ToUnixTimeMilliseconds().ToString(), ClaimValueTypes.Integer64)
};
if (claims != null)
{
claimList.AddRange(claims);
}
var jti = claimList.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Jti)?.Value;
if (jti is null)
{
jti = _tokenOptions.JtiGenerator?.Invoke() ?? GuidIdGenerator.Instance.NewId();
claimList.Add(new(JwtRegisteredClaimNames.Jti, jti));
}
var jwt = new JwtSecurityToken(
issuer: _tokenOptions.Issuer,
audience: _tokenOptions.Audience,
claims: claimList,
notBefore: now.UtcDateTime,
expires: now.Add(_tokenOptions.ValidFor).UtcDateTime,
signingCredentials: _tokenOptions.SigningCredentials);
var encodedJwt = _tokenHandler.WriteToken(jwt);
var response = new TokenEntity()
{
AccessToken = encodedJwt,
ExpiresIn = (int)_tokenOptions.ValidFor.TotalSeconds
};
return response;
}
}
More
默认是基于 HS256 的签名方式,你也可以很轻松地切换成使用基于 RSA 的 RS256 方式,可以自己探索一下
更多实现细节可以参考源码:https://github.com/WeihanLi/WeihanLi.Web.Extensions/tree/dev/src/WeihanLi.Web.Extensions/Authorization/Jwt