JWT?
什么是JWT?
json web token
是一种验证授权的东西。后端生成一个token令牌,返回给前端,前端自行保存。下次请求的时候带上这个token令牌,才有权限,不然没有权限。
token令牌什么样子?
token令牌用.
分成三份
header.payload.signature
第一段是header的base64编码,第二段是payload的base64编码。第三段是加密算出来的签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJyZWZyZXNoIiwibmJmIjoxNjI2MDg1NTA4LCJleHAiOjE2MjYwODYxMDgsImlzcyI6IuW8oOS4iSIsImF1ZCI6IuadjuWbmyJ9.UpnhTDhjwUX4tZrrvpviXFKDdzfXOfVPkD598lAeH0o
这个网站可以查看jwt.io
header是啥?
一个json格式,里面包含了,使用的加密算法等信息
{
"alg": "HS256",
"typ": "JWT"
}
payload是啥?
也是一个json格式,里面包含了一些自带定义的东西(创建token事可以选择不加),过期时间,颁发令牌的是谁,验证令牌的是谁,还可以自定义加键值对。
{
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "refresh",
"nbf": 1626085508,
"exp": 1626086108,
"iss": "张三",
"aud": "李四"
}
signature是啥?
一个签名,防篡改token的。
前面的header和payload,都会编码成base64字符串。服务的还有一个key,就是密钥。signature签名就是header的base64字符串 加上 payload的base64字符串,再用密钥key进行某种算法的加密而生成的。
怎么个防篡改法?
header和payload里面的东西是公开的,base64防小人不妨君子啊。最好不要在payload里面放敏感信息,如密码等。因为有心人可以查看。
如果实在要放点什么信息,加密后在放。
整个token防篡改的最关键地方就在于最后的签名。
签名通过header和payload和key一起算出来的。
服务端一般在登录的时候给客户端发送一个token,之后的客户端请求服务的,都要带上这个token在请求头里面。服务端接到了这个请求,根据请求头里面携带的token的header和payload加上服务端设置的key密钥进行签名计算。算出来的签名如果和发过来的签名不一样,就证明header或者payload被修改了。验算不通过。
只要密钥key在服务端不被盗取,就是安全的。哪怕header和payload被修改了一点点,签名算出来都截然不同。
但是token被被人拿到了,在有效期内他都可以通过这个token进行访问。jwt是无状态的。不认用户,只认token。
安装包
Microsoft.AspNetCore.Authentication.JwtBearer
生成token
public string GenerateAccessToken(JWTData_json jwtdata)
{
string strjson = JsonConvert.SerializeObject(jwtdata);
//主要的内容
var claims = new Claim[]
{
//可以自定义键值对,也可以使用ClaimTypes里面定义的一些东西
//这里设置Role,到后面验证token的时候,可以验证哪些Role可以通过鉴权
new Claim("strKEY",strjson),
new Claim(ClaimTypes.Role,"access")
};
//密钥,保存在配置文件的
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
//这是创建token
var token = new JwtSecurityToken(
issuer: "张三", //需要设置发布token的是谁
audience: "李四", //接受验证token的是谁,这两一般是主机IP地址
claims: claims, //这里是payload
notBefore: DateTime.Now,
expires: DateTime.Now.AddMinutes(5),//过期时间,一般授权token过期时间很短,只有几分钟,免得被人拿到了一直用
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) //使用签名算法
);
//最终生成了token字符串
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}
Startup配置
ConfigureServices
//添加jwt授权
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"])),
ValidateIssuer = true,
ValidIssuer = "张三",
ValidateAudience = true,
ValidAudience = "李四",
ValidateLifetime = true,
//大概相当于缓存时间,令牌时间+这里的时间=真正不能使用token的时间
ClockSkew = TimeSpan.FromSeconds(20)
};
});
Configure
//注意这两玩意的顺序,长得太像了
app.UseAuthentication();
app.UseAuthorization();
验证授权
在控制器上面加上一个特性。这个控制器的方法都要验证token,或者单独加在控制器方法上面,加了的验证,不加的不验证。
//验证所有的token
[Authorize]
//指定设置了Role为access的token才能授权,没有设置Role不是access的哪怕token是对的,都无法鉴权,Role不对好像报405错误
[Authorize(Roles = "access")]
//不用token都可以访问,所有所有访问
[AllowAnonymous]
刷新令牌
上面说了一个进入令牌几分钟就过期了。过期了就无法进入页面授权。短短的几分钟,特别影响体验。
但是进入授权令牌又不能设置时间太长,太长的话,要是被坏人拿到了就可以无限请求。如果时间很短,被坏人拿到了,能减少损失。
为了解决上面的问题。
我们在用户登录的时候给他发两个令牌,一个进入令牌,一个刷新令牌。
进入令牌:时间很短,每次请求都带上,进行鉴权。如果鉴权不通过就会报响应码401。过期了,token就失效了,同样的 不通过,也是401。但是不能说401就是过期了。
刷新令牌:时间长一点,每次请求当进入令牌失效了,就用刷新令牌发送一个请求到专门换令牌的接口,生成一个新的进入令牌替换原来的进入令牌。
比如:用户在页面填写表单,进入令牌过期了,他并不知道。当他点击提交,执行A方法,A方法发送数据到服务端,由于进入令牌过期了,报401错误,A方法里面的Ajax ,catch捕捉到401错误,就把拿出珍藏多时的刷新令牌,发送到一个指定的API接口,最好把过期的令牌一起发过去,或者发点别的东西过去。刷新令牌鉴权通过了,(最好再验证原来的令牌,或者接受的神秘参数正不正确之类的验证),验证刷新令牌成功后,服务端,再次生成一个进入令牌,返回。ajax的catch里面的ajax的请求返回结果如果成功了,拿到了新的进入令牌,就替换原来的进入令牌。然后调用A方法(A方法内部调用A方法),这时候在执行A方法,把数据发送到服务端,由于进入令牌被替换了新的,这次成功了。 反之如果刷新令牌都鉴权不通过,401,说明可能刷新令牌也过期了。刷新令牌都过期了,就重新回到登录页面,账号密码登录成功了就可以得到两个新的令牌了
如上的操作,用户丝毫感觉不到进入令牌过期,一气呵成。
刷新令牌和上面的生成操作一样,可能刷新令牌的payload没啥东西放,进入令牌的payload可以放点需要用到的参数。
public string GenerateRefreshToken()
{
//主要的内容
var claims = new Claim[]
{
new Claim(ClaimTypes.Role,"refresh")
};
//密钥
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
//这是创建token
var token = new JwtSecurityToken(
issuer: "张三",
audience: "李四",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
//最终生成了token字符串
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}
获取令牌里面payload包含的数据
/// <summary>
/// 得到token里面的内容
/// </summary>
public JWTData_json GetAccessPayload()
{
string token = _accessor.HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
string payload = JwtPayload.Base64UrlDeserialize(token.Split(".")[1])["strKEY"].ToString();
//还应该有个解密环节,暂留
//序列化json
return JsonConvert.DeserializeObject<JWTData_json>(payload);
}
前端携带Token请求
发送请求的时候,设置请求头,KEY为Authorization
,VALUE为Bearer
+token
Bearer后面要打一个空格,然后加上token字符串
参考
ASP.NET Core Web Api之JWT(一) ASP.NET Core Web Api之JWT VS Session VS Cookie(二) ASP.NET Core Web Api之JWT刷新Token(三) ASP.Net Core 3.1 中使用JWT认证