.Net Core MVC 基于Cookie进行用户认证
在打代码之前先说一下思路。
登录的的时候服务端生成加密的字符串(用户名、id、当前时间)并且存入客户端cookie中,服务端的缓存中。对客户端的每次请求进行拦截,解密保存在cookie中的加密字符串。查看是否已过期,如果已过期跳转到登录页,并且删除cookie与缓存中的数据。如未过期修改缓存中的时间,并进行下一步操作。
加密解密的代码
引入包:Microsoft.Extensions.Configuration(读配置文件的时候会用到,加密key与解密key最好存在配置文件中,从配置文件中读取)
public class Encryption { private IConfiguration _configuration; public Encryption(IConfiguration configuration) { _configuration = configuration; } public byte[] key() { return ASCIIEncoding.ASCII.GetBytes(_configuration["DES:rgbKey"]); } public byte[] vector() { return ASCIIEncoding.ASCII.GetBytes(_configuration["DES:rgbIV"]); } /// <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 { ms = new MemoryStream(); cs = new CryptoStream(ms, des.CreateEncryptor(this.key(), this.vector()), 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.key(), this.vector()), 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(不需要认证) //反射得到控制器的type Type typeController = Type.GetType($"{controllerActionDescriptor.ControllerTypeInfo.FullName}"); //先判断控制器上是否有这个特性 isDefined = typeController.IsDefined(typeof(AllowAuthAttribute), true); //判断方法上是否有这个特性 isDefined = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true).Any(a => a.GetType().Equals(typeof(AllowAuthAttribute))) ? true : isDefined; } 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 = "用户名或密码输入错误" }); } }
如有不足,还望见谅!😉
作者:江北
出处:https://www.cnblogs.com/zhangnever/p/12726898.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
微信:CodeDoraemon
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现