由于工作需要写一个下载文件的api,要求要有jwt token验证,想着正好可以在Core中试试水,下面是整个开发过程;
由于开始并不懂jwt,在网上找了些相关的资料,对于使用Core api + jwt的文章并不多,这个文章看起来有很多东西:https://www.cnblogs.com/CreateMyself/p/11123023.html,下面是跟着此文章进行的操作,以及自己的一些想法;
jwt由三部分构成,基本范式是:
详细说明:
1.第一部分以Base64编码的Header主要包括Token的类型和所使用的算法,我的理解是定义的一个标准模板,基本使用范式即可
2.第二部分以Base64编码的Payload主要包含的是声明(Claims);我的理解是这个地方携带一些用户自定义的信息,但由于jwt的原理(下面会说),第一部分和第二部分使用的加密只是base64的编码转换,不存在加密,所以保存的信息都是次要信息
3.第三部分则是将Key通过对应的加密算法生成签名,最终三部分以点隔开;也就是通过自己的key把上面两部分加密,且这个加密是不可逆的,也就是只会存在前面两部分被修改,或者第三部分被修改,但都会失去自洽性,也就是通过这个验证;
虽然原理说起来简单,但实际操作起来就头大了,下面说下整个过程,
首先,我们先建立一个Core 3.0版的控制台,然后把必要的分层什么的建立起来
基本的分层没什么说的,mvc架构加上连数据库的MongoFactory,定义zip生成和下载方法的Ziphelper,由于不是本节重点,此处不做演示;(tokenHlper请忽略)
下面我们开始考虑加上jwt验证,都知道现在在vs里搞东西第一步就是去拿Nuget包,支持jwt的需要这两个包
System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authentication.JwtBearer
然后我们需要生成jwt的token
在这我直接在 zipHelper里面写了个GetToken的方法:
public string GetToken()
{
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, "BY"),//此处我的理解是用户自定义部分,也就是jwt中第二部分的数据, //JwtRegisteredClaimNames是微软定义的一些key,其实完全可以自定义
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234xxxxxxxx23456"));//秘钥,这个不能暴露 长度至少是16位,否则会报错
var token = new JwtSecurityToken(
//issuer: "http://localhost:5000",
//audience: "http://localhost:5001",
claims: claims,
notBefore: DateTime.Now,//自动给Claim里面带了个时间戳参数nbf
expires: DateTime.Now.AddMinutes(10),//自动给Claim里面带了个过期戳参数exp
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}
issuer代表颁发Token的Web应用程序,audience是Token的受理者,如果是依赖第三方来创建Token,这两个参数肯定必须要指定,因为第三方本就不受信任,如此设置这两个参数后,我们可验证这两个参数。要是我们完全不关心这两个参数,可直接使用JwtSecurityToken的构造函数来创建Token,如下:
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, "Jeffcky"),
new Claim(JwtRegisteredClaimNames.Email, "[email protected]"),
new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"),
new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMilliseconds(1)).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));
var jwtToken = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));
然后是验证,需要在Startup里的ConfigureServices方法里加上使用jwt组件
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")),
ValidateIssuer = false,
//ValidIssuer = "http://localhost:5000",
ValidateAudience = false,
//ValidAudience = "http://localhost:5001",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("act", "expired");
}
return Task.CompletedTask;
}
};
});
最后别忘记添加认证中间件在Configure方法中,认证中间件必须放在使用MVC中间件之前,如下:
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
然后是前端页面上
验证token的方法是直接取,看是不是能取到,就拿我这个项目里面验证的方法CreateZipFile的时候
//var Claims = User.Claims.Count(); //正常是可以获取到所有的Claims的,我试验如果吧token的串修改的话这个地方就取不到了,但还是会进来这个方法,所以要判断下是不是null
var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;
if (string.IsNullOrEmpty(sub))
{
return Json(new { code="0",id="",strErr="Token验证失败"});
}
var result = _Ziphelper.CreateFileAndZip(id);
return Json(new { code = result.Code, Id = result.Id, strErr = result.strErr });
暂时只有这么多了,这只是用了最简单的...
主逻辑是把key给使用api的那边了,毕竟登录的模块没在我这边,然后他那边生成token之后,发过来这边验证,返回验证信息,如果通过的话会生成zip文件,返回值判断正常就可以调用第二个参数把生成的对应文件下载下来
现在关于jwt的只有生成和验证,后面如果继续深入的话可以让jwt实现刷新,这个我继续跟踪那个博文看着比较复杂,还没有实现。