C#开发日记:手把手教你生成JWT
JWT(JSON Web Token)是一种用于在网络应用环境间传递声明的一种紧凑的、URL安全的方式。它由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。一般是user id(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
一、JWT(JSON Web Token)简介
JWT(JSON Web Token)是一种用于在网络应用环境间传递声明的一种紧凑的、URL安全的方式。它由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。
一般是user id(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
Header(头部)
JWT的头部通常由两部分组成:token的类型(这里是JWT)和所使用的签名算法(这里是HS256,即HMAC-SHA256)。
{
"alg": "HS256",
"typ": "JWT"
}
alg
:指定了签名使用的算法,HS256表示使用HMAC-SHA256算法。typ
:表示这个token是一个JWT。
Payload(负载)
负载部分包含了所谓的Claims(声明),它们是关于实体(通常是用户)和其他数据的声明。在您提供的token中,负载包含了以下声明:
{
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "string",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid": "1828337手动打码461696",
"AuthType": "User",
"nbf": 1724833354,
"exp": 1724840554,
"iss": "dlht",
"aud": "string"
}
name
:用户的名称或昵称。sid
:用户的会话ID或唯一标识符。AuthType
:认证类型,这里是"User"。nbf
:(Not Before)表示token在此时间之前无效。exp
:(Expiration Time)表示token的过期时间。iss
:(Issuer)表示token的发行者。aud
:(Audience)表示token的接收者。
Signature(签名)
签名用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的token,还可以验证发送者的身份。签名是使用Base64Url编码的Header、Payload和密钥(Secret)通过指定的算法生成的。
Signature: vlJkgZ5CMszamjDr_DXKsB-9Wu0CsrczZ5yu49t1ZaQ
二、配置文件(appsettings.json)
使用Token过滤访问你程序的用户,本次以ASP .NET Core Web应用程序为例,使用SqlSugar的ORM框架,Oracle数据库 ,当然数据库不是主角,无伤大雅!!我的思路是先配置参数后再学你怎么用!
重点使用:Microsoft.IdentityModel.Tokens;
"JWT_SECRET_KEY": "asldfgjhopq347yr5t9o2qy8fioqasldasweiuedfghd87dasdqa24fhpdeyf870tyq234o87fghawo87yfaow8eyg7hfosgtyfukoawesty7o8",
"JWT_EXPIRES_SECONDS": "7200"
键 | 介绍 |
JWT_SECRET_KEY | 密钥 |
JWT_EXPIRES_SECONDS | 有效秒数 |
三、注入依赖(program.cs)
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables();
...
//注册JwtSercie,并配置JWT
builder.Services.AddScoped<IJwtSercie, JwtSercie>().Configure<JwtSetting>(options =>
{
options.SecretKey = builder.Configuration["environmentVariables:JWT_SECRET_KEY"];
options.ExpiresSeconds = int.Parse(builder.Configuration["environmentVariables:JWT_EXPIRES_SECONDS"] ?? throw new ArgumentException("请配置JWT_EXPIRES_SECONDS环境变量"));
...
其实就是把json的两个参数拿出来注入了IServiceCollection,具体是设计了JwtSetting的类型,如下所示:
public class JwtSetting
{
public string? SecretKey { get; set; }
public int ExpiresSeconds { get; set; }
}
Configure<JwtSetting>()方法按照这个类型注入两个依赖项。
下面模拟用户登录,首先数据库验证账户与密码,成功后返回token。
设计一个简单用户表
ID(雪花) | USERNAME | PASSWORDHASH |
四、编写服务Service
3.1 JWT服务
GeneratorToken传入一个用户,根据用户信息生成Token(目前我们只有用户名)。
public class JwtSercie(IOptions<JwtSetting> jwtSetting) : IJwtSercie
{
public string GeneratorToken(Entities.USER_Test user)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // 使用用户ID作为NameIdentifier
//new Claim(ClaimTypes.Name, user!.username!),//使用用户名
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim("AuthType", "User"),
};
return WriteToken(user.username!, claims);
}
private string WriteToken(string audience, Claim[] claims)
{
var sourceData = jwtSetting.Value.SecretKey ?? throw new NullReferenceException();
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(sourceData));
const string algorithm = SecurityAlgorithms.HmacSha256;
var signingCredentials = new SigningCredentials(secretKey, algorithm);
var jwtSecurityToken = new JwtSecurityToken(
issuer: "dlht",
audience: HttpUtility.UrlEncode(audience),
claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddSeconds(jwtSetting.Value.ExpiresSeconds),
signingCredentials
);
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return token;
}
}
-
GeneratorToken方法:
- 这个方法接受一个
USER_Test
类型的用户实体作为参数,并生成一个JWT。 - 首先,它创建了一个
Claim
数组,这个数组包含了JWT中要声明的属性。这里包括了用户的用户名(ClaimTypes.Name
)、用户ID(ClaimTypes.Sid
)和认证类型("AuthType")。 - 然后,调用
WriteToken
方法来实际生成JWT。
- 这个方法接受一个
-
WriteToken方法:
- 这个方法接受
audience
(观众,即JWT的接收者)和claims
(声明)作为参数。 - 首先,从
jwtSetting
中获取SecretKey
,如果没有提供则抛出异常。 - 将
SecretKey
转换为SymmetricSecurityKey
类型的密钥,这是用于签名JWT的密钥。 - 定义签名算法为
HmacSha256
。 - 创建
SigningCredentials
对象,包含密钥和算法。 - 创建
JwtSecurityToken
对象,包含以下属性:issuer
(发行者):设置为"dlht"。audience
(观众):使用HttpUtility.UrlEncode
对观众进行URL编码。claims
:之前创建的声明数组。notBefore
(生效时间):设置为当前时间。expires
(过期时间):当前时间加上jwtSetting
中配置的过期秒数。signingCredentials
:签名凭据。
- 使用
JwtSecurityTokenHandler
的WriteToken
方法将JwtSecurityToken
对象序列化为字符串形式的JWT。
- 这个方法接受
如果有好好看第一章的话,WriteToken方法便好理解一些。
3.2 用户服务
若想再用户服务中使用JWT服务的方法,需要在构造函数中注入 IJwtSercie 。
public class UserService([FromKeyedServices("local")] ISqlSugarClient _db, IJwtSercie JwtSercie) : IUserService
{
public async Task<BaseResponse> UserLogin(string username, string password)
{
// 使用SqilSugar查询数据库
var user = await _db.Queryable<USER_Test>()
.Where(u => u.username == username && u.PasswordHash == HashPassword(password))
.FirstAsync();
if (user == null)
return new BaseResponse() { Code = -1, Message = "用户名或者密码错误" };
string token = JwtSercie.GeneratorToken(user ?? throw new NullReferenceException());
return new BaseResponse() { Code = 1, Data= token,Message="登录成功"};
}
}
user返回查询用户的信息,若用户存在则将用户传入GeneratorToken中并返回Token
五 编写控制器
[Route("api/[controller]")]
[ApiController]
public class LoginController(IUserService _userService, IAuthenticationService _authenticationService) : ControllerBase
{
//登录 token
[HttpPost("[action]")]
public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
{
var result = await _userService.UserLogin(loginModel.Username, loginModel.Password);
if(result.Code!=1)
return Unauthorized(result);
return Ok(result);
}
}
六、测试以及分析
JWT的通常遵循Base64Url编码的规则,可能让它看起来没有明显的规律,特别是当它被编码后。让我们可以逐步解析。
例如token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjE4MjgzMzc4MzY1NDY0NjE2OTYiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiIxODI4MzM3ODM2NTQ2NDYxNjk2IiwiQXV0aFR5cGUiOiJVc2VyIiwibmJmIjoxNzI0ODM1MTUwLCJleHAiOjE3MjQ4NDIzNTAsImlzcyI6ImRsaHQiLCJhdWQiOiJzdHJpbmcifQ.4rjl2Bl0OHKnnRQ-yvAw2aoLT48rZt46R-G3tkA6W5E
Header(头部)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
解析后
{
"alg": "HS256",
"typ": "JWT"
}
Payload(负载):
eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjE4MjgzMzc4MzY1NDY0NjE2OTYiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiIxODI4MzM3ODM2NTQ2NDYxNjk2IiwiQXV0aFR5cGUiOiJVc2VyIiwibmJmIjoxNzI0ODM1MTUwLCJleHAiOjE3MjQ4NDIzNTAsImlzcyI6ImRsaHQiLCJhdWQiOiJzdHJpbmcifQ
{
"sub": "42",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
iat
声明代表的是“Issued At”,即token的签发时间
Signature(签名)
4rjl2Bl0OHKnnRQ-yvAw2aoLT48rZt46R-G3tkA6W5E
使用Header中指定的算法和密钥生成的签名。
JWT的编码方式是Base64Url编码,它是一种特殊的Base64编码,用于URL和文件名的安全传输。Base64Url编码与标准的Base64编码相比,有以下不同:
- 将
+
替换为-
- 将
/
替换为_
- 去掉填充字符
=
由于Base64Url编码的特性,JWT看起来可能没有明显的规律,特别是当它被编码后。但是,解码后,您可以清楚地看到JWT的每个部分都是JSON格式的,并且包含了必要的声明和元数据。
注册后续
JWT服务注册于控制器的访问授权设置请看下一章——使用生成的JWT设置资源接口验证 http://t.csdnimg.cn/rgU9r
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)