Asp.net Core 3.1 Web API添加jwt验证(二):用单例模式简单封装token生成器JwtGenerator
2020/7/3 16:26:34
本文主要是介绍Asp.net Core 3.1 Web API添加jwt验证(二):用单例模式简单封装token生成器JwtGenerator,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
提出问题
在上一篇我们搭建了一个基础的项目框架,并介绍了怎么向其中引入jwt鉴权,不知小伙伴们有没有注意到我们用于生成token的代码片段:
[HttpGet("login")] public ActionResult Login(string username, string password) { if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { // token中的claims用于储存自定义信息,如登录之后的用户id等 var claims = new[] { new Claim("userId", username) }; // 获取SecurityKey var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Authentication")["SecurityKey"])); var token = new JwtSecurityToken( issuer: _configuration.GetSection("Authentication")["Issure"], // 发布者 audience: _configuration.GetSection("Authentication")["Audience"], // 接收者 notBefore: DateTime.Now, // token签发时间 expires: DateTime.Now.AddMinutes(30), // token过期时间 claims: claims, // 该token内存储的自定义字段信息 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法 ); // 返回成功信息,写出token return Ok(new { code = 200, message = "登录成功", data = new JwtSecurityTokenHandler().WriteToken(token) }); } // 返回错误请求信息 return BadRequest(new { code = 400, message = "登录失败,用户名或密码为空" }); }
在这段代码里,我们着重看下面这一段:
// token中的claims用于储存自定义信息,如登录之后的用户id等 var claims = new[] { new Claim("userId", username) }; // 获取SecurityKey var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Authentication")["SecurityKey"])); var token = new JwtSecurityToken( issuer: _configuration.GetSection("Authentication")["Issure"], // 发布者 audience: _configuration.GetSection("Authentication")["Audience"], // 接收者 notBefore: DateTime.Now, // token签发时间 expires: DateTime.Now.AddMinutes(30), // token过期时间 claims: claims, // 该token内存储的自定义字段信息 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法 ); // 返回成功信息,写出token return Ok(new { code = 200, message = "登录成功", data = new JwtSecurityTokenHandler().WriteToken(token) });
从上面代码可以看出,要想生成一个完整的token,我们至少需要知道6个类:
- Claim:向token中添加自定义信息
- SymmetricSecurityKey:使用对称方法生成秘钥
- JwtSecurityToken:初始化JwtToken
- SigningCredentials:使用秘钥以及算法生成加密证书
- SecurityAlgorithms:保存了加密方法字符串的常量
- JwtSecurityTokenHandler:JwtToken处理器
理论上来说,框架封装时对外暴露的类型及方法应越少越好(使用者只要知道尽可能少的几个类就可以实现预期的功能),基于此出发点,我们可以使用设计模式对这个生成token的过程进行改造。
开始改造
先说一说什么是单例模式:
单例模式是GoF总结的23种常见设计模式之一,它保证了在整个程序的运行过程中,有且只有一个调用类的实例。
接下来,就使用单例模式来创建新的Token生成器——JwtGenerator
创建JwtGenerator类
在解决方案中右键项目,创建Services目录,并在其下创建JwtGenerator.cs文件,代码如下:
using System; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication; using System.IdentityModel.Tokens.Jwt; using System.Collections.Generic; using System.Text; namespace JwtTest.Services { public class JwtGenerator { // static保证了本类的对象只有一个,且封装在本类内部 private static JwtGenerator _generator = null; // 用于产生JwtToken的本体生成器 private JwtSecurityToken token = null; // token自定义的Claim信息 private IEnumerable<Claim> Claims { get; set; } = null; // 定义token基础信息 // token的颁发者 private string Issuer { get; set; } = string.Empty; // token的接收者 private string Audience { get; set; } = string.Empty; // 用于颁布token的秘钥 private string SecurityKey { get; set; } = string.Empty; // token的秘钥算法 private string Alg { get; set; } = SecurityAlgorithms.HmacSha256; // token的颁发时间,默认取当前时间 private DateTime NotBefore { get; set; } = DateTime.Now; // token的过期时间,默认30分钟后 private DateTime Expires { get; set; } = DateTime.Now.AddMinutes(30); // 构造函数使用private定义,这样外面就无法通过new构造函数来实例化JwtGenerator类 private JwtGenerator() { } // 这里使用了C#新版本的写法,等同于: // public static JwtGenerator GetInstance() { // if (_generator == null) { // _generator = new JwtGenerator(); // } // return _generator; // } // 第一次调用GetInstance时,会实例化static标注的_generator对象,后续调用会返回已经实例化的对象,从而保证本类只有一个对象 public static JwtGenerator GetInstance() => _generator ??= new JwtGenerator(); // 添加自定义信息 public JwtGenerator AddClaims(IEnumerable<Claim> claims) { _generator.Claims = claims; return _generator; } // 添加发布者 public JwtGenerator AddIssuer(string issuer) { _generator.Issuer = issuer; return _generator; } // 添加接收者 public JwtGenerator AddAudience(string audience) { _generator.Audience = audience; return _generator; } // 添加发布时间 public JwtGenerator AddNotBefore(DateTime notBefore) { _generator.NotBefore = notBefore; return _generator; } // 添加过期时间 public JwtGenerator AddExpires(DateTime expires) { _generator.Expires = expires; return _generator; } // 添加用于生成token的秘钥 public JwtGenerator AddSecurityKey(string securityKey) { _generator.SecurityKey = securityKey; return _generator; } // 添加token生成算法 public JwtGenerator AddAlgorithm(string securityAlgorithm) { _generator.Alg = securityAlgorithm; return _generator; } // 生成token public string Generate() { // 必备参数,若没有初始化,则抛出空指针异常 if (string.IsNullOrEmpty(_generator.SecurityKey)) throw new NullReferenceException("SecurityKey is null"); if (string.IsNullOrEmpty(_generator.Issuer)) throw new NullReferenceException("Issuer is null"); if (string.IsNullOrEmpty(_generator.Audience)) throw new NullReferenceException("Audience is null"); // 调用Generate方法之前,已经调用过上面的Add方法添加了对应的初始化token的参数 _generator.token = new JwtSecurityToken( issuer: this.Issuer, audience: this.Audience, claims: this.Claims, notBefore: this.NotBefore, expires: this.Expires, // 创建token颁发证书 signingCredentials: new SigningCredentials( // 使用秘钥字符串跟加密算法生成加密token的对称加密秘钥 key: new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.SecurityKey)), algorithm: this.Alg ) ); // 调用Token处理器,写出token字符串 return new JwtSecurityTokenHandler().WriteToken(token); } } }
上述代码只是对token生成器的简单封装,token的接收者可能有很多个,这时我们可以参考上面的AddXXXX方法,添加AddAudiences功能,其他功能也一样可以自定义。
接下来我们来体验一下刚刚创建的JwtGenerator
使用JwtGenerator
打开AuthController,添加一个新方法Signin()
// GET: api/auth/signin [HttpGet("signin")] [AllowAnonymous] public ActionResult Signin(string username, string password) { if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { // 调用JwtGenerator,生成一个新的token string token = JwtGenerator.GetInstance() .AddIssuer(_configuration.GetSection("Authentication")["Issure"]) .AddAudience(_configuration.GetSection("Authentication")["Audience"]) .AddSecurityKey(_configuration.GetSection("Authentication")["SecurityKey"]); .AddClaims(new[] { new Claim("userId", username) }).Generate(); return Ok(new { code = 200, message = "登录成功", data = new { token } }); } return BadRequest(new { code = 400, message = "登录失败,用户名或密码不能为空" }); }
运行程序,使用Postman测试GET /api/auth/signin接口吧。
再次思考
经过上面步骤,我们已经封装好了JwtGenerator,代码调用虽然简单了,但整体看上去并没有什么很大的优化。
请跟随我的脚步继续思考
实际开发过程中我们经常会遇到这样一个场景:
用户登录时颁发一个token、用户需要重置密码时颁发一个token、敏感资源访问时又颁发一个token
token经常需要携带允许访问的动作信息供后端校验,来保证自身不被用于其他接口
也就是说在整个后端代码中可能有不少地方我们都会生成新的token,难道每次我们都需要这么写?
string token = JwtGenerator.GetInstance() .AddIssuer(_configuration.GetSection("Authentication")["Issure"]) .AddAudience(_configuration.GetSection("Authentication")["Audience"]) .AddSecurityKey(_configuration.GetSection("Authentication")["SecurityKey"]); .AddClaims(new[] { new Claim("userId", username) }).Generate();
其实不然,仔细思考一下,不管新的token怎么变,总是有一些参数不会变的,比如说秘钥。
那么由此,我们就可以得到下面的写法
打开Startup.cs,在ConfigureServices方法中添加下面代码:
// 配置Token生成器 JwtGenerator.GetInstance() .AddIssuer(_configuration.GetSection("Authentication")["Issure"]) .AddAudience(_configuration.GetSection("Authentication")["Audience"]) .AddSecurityKey(_configuration.GetSection("Authentication")["SecurityKey"]);
因为我这个项目,Issuer跟Audience,SecurityKey都不会变,所以我在Startup中拿到JwtGenerator的实例,并初始化了其中不会改变的配置。
这样我就可以在需要的地方这么调用它:
// 登录鉴权动作 string token = JwtGenerator.GetInstance() .AddClaims(new[] { new Claim("action", "login"), new Claim("uid", username) }).Generate(); // 重置密码动作 string token = JwtGenerator.GetInstance() .AddClaims(new[] { new Claim("action", "reset"), new Claim("uid", username) }).Generate(); // 敏感资源访问动作 string token = JwtGenerator.GetInstance() .AddClaims(new[] { new Claim("action", "oauth"), new Claim("source", "/api/admin/xxx") }).Generate();
怎么样。这token生成操作可还行?
这篇关于Asp.net Core 3.1 Web API添加jwt验证(二):用单例模式简单封装token生成器JwtGenerator的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-05-08首个适配Visual Studio平台的国产智能编程助手CodeGeeX正式上线!C#程序员必备效率神器!
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#
- 2024-01-24Advanced .Net Debugging 1:你必须知道的调试工具
- 2024-01-24.NET集成IdGenerator生成分布式全局唯一ID
- 2024-01-23用CI/CD工具Vela部署Elasticsearch + C# 如何使用
- 2024-01-23.NET开源的简单、快速、强大的前后端分离后台权限管理系统