在.net Core 3.0中使用jwt验证
由于工作需要写一个下载文件的api,要求要有jwt token验证,想着正好可以在Core中试试水,下面是整个开发过程;
由于开始并不懂jwt,在网上找了些相关的资料,对于使用Core api + jwt的文章并不多,这个文章看起来有很多东西:https://www.cnblogs.com/CreateMyself/p/11123023.html,下面是跟着此文章进行的操作,以及自己的一些想法;
jwt由三部分构成,基本范式是:
- {"alg":"HS256","typ":"JWT"}
- {"sub":"BY","nbf":1575448305,"exp":1575448905}
- 加密串1234567890123456
详细说明:
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?}"); });
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
@*<a id="btn">测试</a>*@
<input type="button" id="btn" value="模拟登录获取Token" />
<input type="button" id="btn-currentTime" value="调用客户端获取当前时间" />
</div>
<script src="~/js/jquery-latest.js"></script>
<script type="text/javascript">
$(function () {
$('#btn').click(function () {
var token = null;
var getToken= $.ajax({
url: "@Url.Action("GetToken","Zip")",
type: 'get',
contentType: 'application/json',
data: {},
dataType: "Json",
success: function (data) {
token = data ;
console.info(token)
CreateZipFile()
},
error: function (xhr) {
alert(xhr.status);
}
});
function CreateZipFile() {
$.ajax({
url: "@Url.Action("CreateZipFile","Zip")",
type: 'get',
contentType: 'application/json',
data: {},
dataType: "Json",
beforeSend: function (xhr) {
console.info(token)
if (token !== null) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
},
success: function (data) {
console.info(data);
console.info(2)
if (data.code == "200") {
window.open("api/Zip/downloadRequest?id=" + data.id);
}
else {
console.info(data.strerr);
}
},
error: function (xhr) {
alert(xhr.status);
}
});
}
});
</script>
验证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文件,返回值判断正常就可以下载了