在打代碼之前先說一下思路。
登錄的的時候服務端生成加密的字符串(用戶名、id、當前時間)並且存入客戶端cookie中,服務端的緩存中。對客戶端的每次請求進行攔截,解密保存在cookie中的加密字符串。查看是否已過期,如果已過期跳轉到登錄頁,並且刪除cookie與緩存中的數據。如未過期修改緩存中的時間,並進行下一步操作。
加密解密的代碼
引入包:Microsoft.Extensions.Configuration(讀配置文件的時候會用到,加密key與解密key最好存在配置文件中,從配置文件中讀取)
public class Encryption { private IConfiguration _configuration; public Encryption(IConfiguration configuration) { _configuration = configuration; } public byte[] EncrKey() { return ASCIIEncoding.ASCII.GetBytes(_configuration["MD5:EncrKey"]); } public byte[] DecrKey() { return ASCIIEncoding.ASCII.GetBytes(_configuration["MD5:DecrKey"]); } /// <summary> /// DES加密 /// </summary> /// <param name="str"></param> /// <returns></returns> public async Task<string> Encrypt(string str) { MemoryStream ms = null; CryptoStream cs = null; StreamWriter sw = null; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); try { var s= ASCIIEncoding.ASCII.GetBytes(_configuration["MD5:EncrKey"]); ms = new MemoryStream(); cs = new CryptoStream(ms, des.CreateEncryptor(this.EncrKey(), this.DecrKey()), CryptoStreamMode.Write); sw = new StreamWriter(cs); sw.Write(str); sw.Flush(); cs.FlushFinalBlock(); return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length); } finally { if (sw != null) sw.Close(); if (cs != null) cs.Close(); if (ms != null) ms.Close(); } } /// <summary> /// DES解密 /// </summary> /// <param name="str"></param> /// <returns></returns> public async Task<string> Decrypt(string str) { MemoryStream ms = null; CryptoStream cs = null; StreamReader sr = null; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); try { ms = new MemoryStream(Convert.FromBase64String(str)); cs = new CryptoStream(ms, des.CreateDecryptor(this.EncrKey(), this.DecrKey()), CryptoStreamMode.Read); sr = new StreamReader(cs); return sr.ReadToEnd(); } finally { if (sr != null) sr.Close(); if (cs != null) cs.Close(); if (ms != null) ms.Close(); } } }
操作Redis
引入包:StackExchange.Redis
連接字符串最好也存入配置文件中
public class RedisContent { public StackExchange.Redis.ConnectionMultiplexer Redis = null; public StackExchange.Redis.IDatabase db = null; public RedisContent(IConfiguration configuration) { _configuration = configuration; this.Redis = ConnectionMultiplexer.Connect($"{_configuration["Redis:dbconn"]}"); this.db = this.Redis.GetDatabase(); } private IConfiguration _configuration; }
定義一個特性類,對於一些不需要認證的接口,加上這個特性即可。相當於微軟的[AllowAnonymous]認證中間件。這裏的話我們自己寫一個。
public class AllowAuthAttribute : Attribute { }
添加過濾器AuthorizeFilter。上面封裝的一些方法,全部以注入的形式進行使用。
public class AuthorizeFilter : Attribute, IAuthorizationFilter { private readonly ILogger<AuthorizeFilter> _logger; private readonly RedisContent _content; private readonly Encryption _encryption; public AuthorizeFilter(RedisContent content, Encryption encryption, ILogger<AuthorizeFilter> logger) { _content = content; _encryption = encryption; _logger = logger; } public void OnAuthorization(AuthorizationFilterContext context) { var isDefined = false; var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor != null) { //判斷請求的控制器和方法有沒有加上AllowAuthAttribute(不需要認證) isDefined = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true) .Any(a => a.GetType().Equals(typeof(AllowAuthAttribute))); } if (isDefined) return; if (context.HttpContext.Request.Path == "/") return; string value = string.Empty; context.HttpContext.Request.Cookies.TryGetValue("userinfo", out value); if (string.IsNullOrEmpty(value)) context.HttpContext.Response.Redirect("https://localhost:44300/"); else { //解密cookie var decryptValueArray = _encryption.Decrypt(value).Result.Split("|"); string user = _content.db.StringGet($"{decryptValueArray[0]}-{decryptValueArray[2]}"); if (string.IsNullOrEmpty(user) || !user.Equals(value)) { _logger.LogError($"Token已過期/有誤! Url:{context.HttpContext.Request.Path}"); context.HttpContext.Response.Cookies.Delete("userinfo"); context.HttpContext.Response.Redirect("https://localhost:44300/"); } else { //重新設置key的時間 _content.db.KeyExpire($"{decryptValueArray[0]}-{decryptValueArray[2]}", TimeSpan.FromMinutes(30)); return; } } } }
編寫控制器中的代碼
/// <summary> /// 驗證登錄 頒發Token 存入cookie /// </summary> /// <param name="userName"></param> /// <param name="password"></param> /// <returns></returns> [AllowAuth] public async Task<IActionResult> LoginUser(string userName, string password) { if (string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password)) return Json(new { success = false, msg = "用戶名或密碼不能爲空" }); //帶參查詢 var data = await _userInfoServices.QueryUserInfo(userName, password); if (data.Any()) { //得到uid,將uid也帶進token中加密 var uid = data.ToList()[0].id; //加密 30分鐘的有效期 var token = await _encryption.Encrypt(userName + "|" + DateTime.Now + "|" + uid + "|" + 30); //存入redis中 _content.db.StringSet($"{userName}" + "-" + uid, token, TimeSpan.FromMinutes(30)); //存入cookie中 Response.Cookies.Append("userinfo", token); return Json(new { success = true, msg = "登陸成功" }); } else { return Json(new { success = false, msg = "用戶名或密碼輸入錯誤" }); } }
如有不足,還望見諒!😉