上一篇学习了JWT的基本理论,这一篇将根据原理进行代码实现。
要想实现jwt的加密解密,要先生成一个SecurityKey,大家可以在网上工具生成一个随机的密钥。我是在这里生成的。
下面篇幅大量都是代码,因为注释写得很清楚,因此就不再有过多文字说明。
代码实现
新建常量类:Const
public class Const { public const string SecurityKey= "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSfLGu+kcFDcJUCV46J+SbgR0lNc2NqgCGzojQTWW9xqjuzPF3mpisvTggYZSGfBzN+88YLZYbBLrDTUMJ4nTieElbP6SHkBFu8F+7fFBi7w3UPsaAXDr2E2srQYU5ZlKAcFBoNajNWj3sfSVRoYRPdqDTj4WdJlUPSNGz0wgRrQIDAQAB"; public const string Domain = "http://localhost:5000"; }
新建控制器:AuthController
[ApiController] [Route("[controller]")] public class AuthController : ControllerBase { [HttpGet] public IActionResult Get(string userName, string pwd) { //此处只简单的验证用户名和密码的不为空,实际中使用时不要这样 if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(pwd)) { //Header var header = "{"alg": "HS256","typ": "JWT"}"; var headerBase = Base64UrlTextEncoder.Encode(Encoding.UTF8.GetBytes(header)); //Payload var payloadDic = new Dictionary<string, object>(); payloadDic["iss"]= Const.Domain; //添加jwt可用时间 var now = DateTime.UtcNow; payloadDic["nbf"] = now.ToUniversalTime();//可用时间起始 payloadDic["exp"] = now.AddMinutes(30).ToUniversalTime();//可用时间结束 var payload = JsonConvert.SerializeObject(payloadDic); var payloadBase = Base64UrlTextEncoder.Encode(Encoding.UTF8.GetBytes(payload)); //Signature //声明hs256对象 var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(Const.SecurityKey)); //生成signature var signature = hs256.ComputeHash(Encoding.UTF8.GetBytes(headerBase + "." + payloadBase)); var signatureBase = Base64UrlTextEncoder.Encode(signature); return Ok(new { token = headerBase + "." + payloadBase + "." + signatureBase }) ; } else { return BadRequest(new { message = "username or password is incorrect." }); } } }
为了过滤哪些接口需要验证,此处新建一个特性:AuthAttribute
public class AuthAttribute : Attribute { public AuthAttribute() { } }
修改原有的Home控制器:
[ApiController] [Route("[controller]")] public class HomeController : ControllerBase { [HttpGet] [Route("api/value1")] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value1" }; } [HttpGet] [Route("api/value2")] [Auth] public ActionResult<IEnumerable<string>> Get2() { return new string[] { "value2", "value2" }; } }
Value2接口标记了Auth特性,在下面验证时有Auth特性标记的接口才会被要求token。
新建静态类:AuthExtension,并且增加一个IApplicationBuilder的扩展方法:
public static class AuthExtension { public static void AddAuthorize(this IApplicationBuilder applicationBuilder) { applicationBuilder.Use(async (currentContext, nextContext) => { //获取是否具有自定义的auth特性 var authAttribute = currentContext.GetEndpoint()?.Metadata.GetMetadata<AuthAttribute>(); if (authAttribute != null) { if (currentContext.Request.Headers.ContainsKey("Authorization")) { var authorize = currentContext.Request.Headers["Authorization"].ToString(); if (authorize.Contains("Bearer")) { var info = authorize.Replace("Bearer ", string.Empty); var jwtStr = info.Split('.').ToArray(); //声明hs256对象 var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(Const.SecurityKey)); //生成signature var signature = Base64UrlTextEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(jwtStr[0] + "." + jwtStr[1]))); //验证加密后是否相等 if (jwtStr[2] == signature) { //验证是否在有效时间内 var now = DateTime.UtcNow.ToUniversalTime(); var payload = JsonConvert.DeserializeObject<Dictionary<string, object>>(Encoding.UTF8.GetString(Base64UrlTextEncoder.Decode(jwtStr[1]))); if (now >= Convert.ToDateTime(payload["nbf"]) && now <= Convert.ToDateTime(payload["exp"])) { //await currentContext.Response.WriteAsync("验证通过").ConfigureAwait(true); await nextContext?.Invoke(); return; } currentContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; await currentContext.Response.WriteAsync("Authorization time has passed, please log in again!").ConfigureAwait(true); } } } currentContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; await currentContext.Response.WriteAsync("Verification failed, no permission to access!").ConfigureAwait(true); } await nextContext?.Invoke(); }); } }
在启动类Startup的请求管道中(Configure)添加上面的扩展方法:
//添加身份验证 app.AddAuthorize();
注意一定要把这句话添加在UseRouting()之后,因为在扩展方法中获取Auth特性只有在注册了Routing规则后才能获取到值。
测试
访问无需权限的Value1接口:
访问成功!!!
获取token:
我启用了swagger,如果没有启用在postman中请求https://localhost:5001/Auth?userName=admin&pwd=admin也是一样的。
代码中用户名和密码我只是简单的验证了下是否为空,所以这里填写什么都能通过。
获取token成功说明获取token的代码没有问题,逻辑有没有问题还不能确定,所以要经过后面接口的确认看是否成功。
访问要求权限验证的Value2接口:
不带token:
访问失败!!!
带上token:
成功!!!
静待三十分钟(代码中设置token过期时间为三十分钟),调用Value2接口:
失败了!!!错误提示是token过期。
如果觉得不保险,还可以逐步调试看一下是否所有逻辑都正确执行,这里就不再进行赘述了。
至此证明我们依照jwt原理写的权限验证成功!!!
源码地址:
说明:Demo-jwt-core2是本篇文章涉及到的源码,是使用asp.net core 自带的jwt方法;
Demo-jwt-core是第三篇jwt文章的源码,在
原文地址:
https://www.cnblogs.com/jingboweilan/p/14590999.html