本文基于.net core webapi进行讲解,使用CSRedis库操作Redis,实现项目的登录模块。登录方式使用手机号、短信验证码进行登录。登录过程包含获取短信验证码、判断用户输入手机号及短信验证码是否正确及刷新登录有效期3个环节。
用户登录第一步先输入手机号码,并获取验证码,后台生成验证码后存储到Redis中,并设置有效期,验证码用字符串格式存储在Redis中,为确保多用户同时发送验证码时验证码唯一,将使用手机号作为Redis的Key。发送手机验证码接口相关代码实现如下:
1、接口Controllers代码
namespace RedisTest.Controllers { [Route("api/[controller]/[action]")] [ApiController] [TypeFilter(typeof(CustomAuthorizeFilter))] public class LoginController : ControllerBase { private readonly IRedisDomainService redisService; public LoginController(IRedisDomainService redisService) { this.redisService = redisService; } /// <summary> /// 发送手机验证码 /// </summary> /// <param name="phone"></param> /// <returns></returns> [HttpPost] [AllowAnonymous] public ResponseContent SendCode(string phone) { //HttpContext.Session.GetString("key"); return redisService.SendCode(phone); } } }
2、短信验证码发送服务代码
using Common.CacheManager; using Common.Commons; using Common.Enum; using Domain.Entities; using Domain.Repository; using Microsoft.AspNetCore.Http; using Common.Const; using Common.Extensions; using System.Security.Policy; namespace Domain.Service { public class RedisDomainService : IRedisDomainService { private readonly IRedisDomainRepostory redisRepostory; private readonly ICacheService cacheService; public RedisDomainService(IRedisDomainRepostory redisRepostory, ICacheService cacheService) { this.redisRepostory = redisRepostory; this.cacheService = cacheService; } /// <summary> /// 发送短信验证码 /// </summary> /// <param name="phone"></param> /// <returns></returns> public ResponseContent SendCode(string phone) { ResponseContent response = new ResponseContent(); //1.校验手机号 if (!RegexUtils.IsMobile(phone)) { //2.如果不符合,返回错误信息 return response.Error(ResponseType.ParameterVerificationError); } //3.符合生成验证码 Random random = new Random(); string code = random.Next(100000, 999999).ToString(); //4.保存验证码到Redis cacheService.Add(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL); //5.发送验证码(模拟发送) Console.WriteLine("短信验证码已经发送,验证码:" + code); //返回ok return response.Ok(code); } } }
用户输入手机号及短信验证码,后端代码将于Redis中存储的短信验证码进行对比,如果一致在进行判断数据库中是否存在用户信息,不存在将进行用户创建,最后将用户信息存储到Redis中,供其他模块使用,存储用户信息将使用Hash类型进行存储,随机生成Guid作为Key值,并将Guid作为Token返回给前端,前端在访问其他接口时需要在Header的Authorization中携带token。关键代码如下:
1、接口Controllers代码
using Common.Commons; using Domain.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; namespace RedisTest.Controllers { [Route("api/[controller]/[action]")] [ApiController] [TypeFilter(typeof(CustomAuthorizeFilter))] public class LoginController : ControllerBase { private readonly IRedisDomainService redisService; public LoginController(IRedisDomainService redisService) { this.redisService = redisService; } /// 登录 /// </summary> /// <param name="phone"></param> /// <param name="code"></param> /// <returns></returns> [HttpPost] [AllowAnonymous] public async Task<ResponseContent> Login(string phone, string code) { return await redisService.Login(phone, code); } } }
2、用户登录验证服务代码
using Common.CacheManager; using Common.Commons; using Common.Enum; using Domain.Entities; using Domain.Repository; using Microsoft.AspNetCore.Http; using Common.Const; using Common.Extensions; using System.Security.Policy; namespace Domain.Service { public class RedisDomainService : IRedisDomainService { private readonly IRedisDomainRepostory redisRepostory; private readonly ICacheService cacheService; public RedisDomainService(IRedisDomainRepostory redisRepostory, ICacheService cacheService) { this.redisRepostory = redisRepostory; this.cacheService = cacheService; } /// <summary> /// 登录 /// </summary> /// <param name="phone"></param> /// <param name="code"></param> /// <returns></returns> public async Task<ResponseContent> Login(string phone, string code) { ResponseContent response = new ResponseContent(); try { //1.校验手机号 if (!RegexUtils.IsMobile(phone)) { //2.如果不符合,返回错误信息 return response.Error(ResponseType.ParameterVerificationError); } //2.校验验证码 //string cacheCode = _session.GetString("code"); string cacheCode = cacheService.Get(RedisConstants.LOGIN_CODE_KEY + phone); if (cacheCode == null || !cacheCode.Equals(code)) { //3.不一致,报错 return response.Error(ResponseType.PINError); } //4.一致,根据手机号查询用户 User user = await redisRepostory.FindOneByMobile(phone); //5.判断用户是否存在 if (user == null) { //6.不存在,创建新用户并保存 Random random = new Random(); user = await redisRepostory.Add(phone, "测试用户" + random.Next(100000, 999999).ToString()); } //7.保证用户信息到session中 //_session.SetString("userMobile", user.Mobile); //7.保证用户信息到Redis中 //7.1随机生成token,作为登录令牌 string token = Guid.NewGuid().ToString(); //7.2将User对象转为Dictionary存储 Dictionary<string, string> userDic = ObjectExtension.ModelToDic(user); //7.3保存数据到Resdis string tokenKey = RedisConstants.LOGIN_TOKEN_KEY + token; cacheService.AddHash(tokenKey, userDic); //7.4设置token有效期 cacheService.Expire(tokenKey, RedisConstants.LOGIN_TOKEN_TTL); //8.返回token return response.Ok(new { token = token }); } catch (Exception e) { return response.Error(404,e.Message); } } } }
以上实现了登录功能,但是用户名信息存储在Redis中,到达过期时间就会被自动删除。解决此问题的方法在需要登录才能访问的接口中,使用IAuthorizationFilter过滤器,过滤器中根据前台传的Token获取Redis中的用户信息,如果获取成功,则刷新过期时间。
1、IAuthorizationFilter实现代码如下:
using Common.CacheManager; using Common.Const; using Common.Dto; using Common.Enum; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Common.Commons { /// <summary> /// 自定义身份验证过滤器,实现登录及权限的验证 /// </summary> public class CustomAuthorizeFilter : Attribute, IAuthorizationFilter { private readonly ICacheService cacheService; public CustomAuthorizeFilter(ICacheService cacheService) { this.cacheService = cacheService; } public void OnAuthorization(AuthorizationFilterContext context) { //当调用的接口是加了[AllowAnonymous]特性的,绕过权限验证的 if (context.ActionDescriptor.EndpointMetadata.Any(item => item is IAllowAnonymous)) { return; } //未登录处理 if (string.IsNullOrEmpty(context.HttpContext.Request.Headers["Authorization"])) { context.Result = new JsonResult(new ResponseContent().Error(ResponseType.LoginExpiration)); return; } //1.获取token string token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault(); //2.基于Token获取Redis中的用户 string tokenKey = RedisConstants.LOGIN_TOKEN_KEY + token; UserDto user = cacheService.GetHash<UserDto>(tokenKey); //3.判断用户是否存在 if (user == null) { //4.不存在,拦截,返回401状态码 context.Result = new JsonResult(new ResponseContent().Error(ResponseType.LoginError)); return; } //5.刷新token有效期 cacheService.Expire(tokenKey, RedisConstants.LOGIN_TOKEN_TTL); //6.放行 return; } } }
2、接口Controllers代码
using Common.Commons; using Domain.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; namespace RedisTest.Controllers { [Route("api/[controller]/[action]")] [ApiController] [TypeFilter(typeof(CustomAuthorizeFilter))] public class LoginController : ControllerBase { private readonly IRedisDomainService redisService; public LoginController(IRedisDomainService redisService) { this.redisService = redisService; } [HttpPost] public async Task<ResponseContent> GetUserInfo(string phone) { ResponseContent response = new ResponseContent(); return response.Ok(new { Message = "获取用户信息成功" }); } [HttpPost] [AllowAnonymous] public async Task<ResponseContent> AllowAnonymousTest() { ResponseContent response = new ResponseContent(); return response.Ok(new { Message = "访问成功" }); } } }
以上代码保证了,只要用户不断访问需要登录的接口,Redis中存储的用户信息就不会过期。
作者初学.net core开发,有不对的地方欢迎评论区批评交流,后续将不断更新本教程。
using CSRedis; using Newtonsoft.Json; using System.Reflection; namespace Common.CacheManager { public class RedisCacheService : ICacheService { public RedisCacheService() { var csredis = new CSRedisClient("127.0.0.1,Password=123456,SyncTimeout=15000"); RedisHelper.Initialization(csredis); } /// <summary> /// 验证缓存项是否存在 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public bool Exists(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return RedisHelper.Exists(key); } /// <summary> /// List头部插入一个元素 /// </summary> /// <param name="key"></param> /// <param name="val"></param> public void LPush(string key, string val) { RedisHelper.LPush(key, val); } /// <summary> /// List尾部插入一个元素 /// </summary> /// <param name="key"></param> /// <param name="val"></param> public void RPush(string key, string val) { RedisHelper.RPush(key, val); } /// <summary> /// 从List尾部删除一个元素 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public T ListDequeue<T>(string key) where T : class { string value = RedisHelper.RPop(key); if (string.IsNullOrEmpty(value)) return null; return JsonConvert.DeserializeObject<T>(value); } /// <summary> /// 从List尾部删除一个元素 /// </summary> /// <param name="key"></param> /// <returns></returns> public object ListDequeue(string key) { string value = RedisHelper.RPop(key); if (string.IsNullOrEmpty(value)) return null; return value; } /// <summary> /// 移除list中的数据,keepIndex为保留的位置到最后一个元素如list 元素为1.2.3.....100 /// 需要移除前3个数,keepindex应该为4 /// </summary> /// <param name="key"></param> /// <param name="keepIndex"></param> public void ListRemove(string key, int keepIndex) { RedisHelper.LTrim(key, keepIndex, -1); } /// <summary> /// 添加一个String类型元素 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireSeconds"></param> /// <param name="isSliding"></param> /// <returns></returns> public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false) { return RedisHelper.Set(key, value, expireSeconds); } /// <summary> /// 添加一个元素 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireSeconds"></param> /// <param name="isSliding"></param> /// <returns></returns> public bool AddObject(string key, object value, int expireSeconds = -1, bool isSliding = false) { return RedisHelper.Set(key, value, expireSeconds); } /// <summary> /// 添加一个Hash类型元素 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireSeconds"></param> /// <param name="isSliding"></param> /// <returns></returns> public void AddHash(string key, Dictionary<string, string> value) { foreach (var item in value) { RedisHelper.HSet(key, item.Key, item.Value); } } public T GetHash<T>(string key) where T : class { //获取所有键值 RedisScan<(string field, string value)> m_pair = RedisHelper.HScan(key, 0); (string field, string value)[] items = m_pair.Items; //构造新实例 T t = Activator.CreateInstance<T>(); try { //获得新实例类型 var Types = t.GetType(); //获得遍历类型的属性字段 foreach (PropertyInfo sp in Types.GetProperties()) { //遍历Hash值 for (int i = 0; i < items.Length; ++i) { //判断属性名是否相同 if (items[i].field == sp.Name) { string propertyType = sp.PropertyType.ToString(); //获得s对象属性的值复制给d对象的属性 if (propertyType.Equals("System.Guid")) sp.SetValue(t, Guid.Parse(items[i].value), null); else sp.SetValue(t, items[i].value, null); } } } } catch (Exception ex) { throw ex; } return t; } public bool Expire(string key, int expireSeconds = -1) { return RedisHelper.Expire(key, expireSeconds); } /// <summary> /// 删除缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public bool Remove(string key) { RedisHelper.Del(key); return true; } /// <summary> /// 批量删除缓存 /// </summary> /// <param name="key">缓存Key集合</param> /// <returns></returns> public void RemoveAll(IEnumerable<string> keys) { RedisHelper.Del(keys.ToArray()); } /// <summary> /// 获取缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public T Get<T>(string key) where T : class { return RedisHelper.Get<T>(key); } /// <summary> /// 获取缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public string Get(string key) { return RedisHelper.Get(key); } public void Dispose() { } } }
- .net core WebApi使用Redis教程(二)Redis缓存应用基础讲解(871)
- Redis教程一、Redis基础讲解及windows下安装、配置说明(633)
- .net core WebApi使用Redis教程(一)Redis代替Session实现登录(562)
- .net core WebApi使用Redis教程(五)Redis缓存应用缓存击穿概念及解决办法(492)
- .net core WebApi使用Redis教程(三)Redis缓存应用缓存穿透概念及解决办法(480)
- .net core WebApi使用Redis教程(四)Redis缓存应用缓存雪崩讲解(404)
- Redis教程二、Redis数据结构及常用命令详细介绍(403)
- Redis教程三、一图读懂Redis企业应用场景(372)
- 2025年3月 (1)
- 2024年6月 (2)
- 2024年5月 (2)
- 2024年4月 (4)
- 2024年3月 (30)
- 2024年1月 (4)
- 2023年12月 (2)
- 2023年11月 (4)
- 2023年10月 (4)
- 2023年9月 (6)
- 2023年3月 (2)
- 2023年2月 (1)
- 2023年1月 (1)
- 2022年12月 (1)
- 2022年9月 (21)
- 2022年8月 (10)
- 2022年7月 (3)
- 2022年4月 (1)
- 2022年3月 (13)
- 2021年8月 (1)
- 2021年3月 (1)
- 2020年12月 (42)
- 2020年11月 (7)
- 2020年10月 (5)
- 2020年8月 (1)
- 2020年6月 (1)
- 2020年3月 (2)
- 2019年12月 (8)
- 2019年11月 (3)
- 2019年9月 (1)
- 2019年4月 (1)
- 2019年3月 (6)
- 2019年2月 (1)
- 2018年7月 (7)
- 1.asp.net mvc内微信pc端、H5、JsApi支付方式总结(5766)
- 2.Windows 10休眠文件更改存储位置(3459)
- 3.各大搜索网站网站收录提交入口地址(3365)
- 4.ECharts仪表盘实例及参数使用详解(3238)
- 5.windows 10安装myeclipse 10破解补丁cracker.jar、run.bat闪退解决办法(3219)
- 6.HTML5 WebSocket与C#建立Socket连接实现代码(3052)
- 7.华为鸿蒙系统清除微信浏览器缓存方法(2975)
- 8.CERT_HAS_EXPIRED错误如何解决(2571)
- 9.Js异步async、await关键字详细介绍(lambda表达式中使用async和await关键字)(2400)
- 10.HBuilder编辑器格式化代码(2288)