ASP.NET Core之身份认证自定义Token
一、前言
在上一个章节,学习了基于JWT的身份认证和授权,本章节则使用自定义Token,结合Redis缓存数据库实现ASP.NET Core的身份认证和授权。自定义Token的方式主要步骤①用户登录成功时候创建Token字符串;②请求服务资源的时候验证Token;③Token过期的失效重新登录;④Token字符串的Redis存储;⑤Token的数据加密;⑥Token的组成内容。完成上述步骤基本实现自定义Token的使用。
二、Token
使用ASP.NET Core、Redis构建自定义token的身份认证和授权,主要包括步骤①注入Redis服务、AddAuthentication认证服务;②启用Authentication认证中间件;③定义自定义认证服务的Handler;④登录验证通过生成token,写入Redis缓存数据中,其中key是生成的token,value是对应的用户信息(非私密数据)。⑤在接口添加特性[Authorize]进行验证;⑥验证通过,使用上下文中获取token验证通过写入的身份信息。
1、在program中完成服务的注入、中间件启用、配置文件的读取。在builder.Services.AddAuthentication中注入认证服务,配置项设置认证Handler、Scheme名称,builder.Services.AddStackExchangeRedisCache注入缓存服务,继承了接口IDistributedCache(Redis、内存、数据库)。
using Microsoft.OpenApi.Models; namespace tqf.LoginMyTokenRedis.demo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); // 添加Bearer认证 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer" }); // 为API添加Bearer认证需求 c.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }, Scheme = "oauth2", Name = "Bearer", In = ParameterLocation.Header, }, new List<string>() } }); }); // 从 appsettings.json 中获取 Redis 连接配置 var redisConnectionString = builder.Configuration.GetSection("RedisCacheSettings:ConnectionString").Value; builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = redisConnectionString; options.InstanceName = builder.Configuration.GetSection("RedisCacheSettings:InstanceName").Value; }); // 自定义认证 builder.Services.AddAuthentication(option => { option.AddScheme<TokenAuthenticationHandler>("token","myToken"); option.DefaultAuthenticateScheme = "token"; option.DefaultForbidScheme = "token"; option.DefaultChallengeScheme = "token"; }); // Add services to the container. builder.Services.AddControllers(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); // 可以在这里配置Swagger UI的其他选项,比如OAuth2客户端配置 }); } // Configure the HTTP request pipeline. app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
2、自定义认证的Handler逻辑,认证的关键在于实现IAuthenticationHandler接口,包含三个要实现的方法,InitializeAsync初始化方法,完成AuthenticationScheme和HttpContext 赋值;AuthenticateAsync认证的具体逻辑,通过获取redis的token是否在或过期,通过则生成身份信息ClaimsIdentity,并且加入上下文中。
using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; using System.Security.Claims; namespace tqf.LoginMyTokenRedis.demo { /// <summary> /// 自定义验证的执行Handler /// </summary> public class TokenAuthenticationHandler : IAuthenticationHandler { /// <summary> /// /// </summary> private readonly ILogger _logger; /// <summary> /// /// </summary> private IDistributedCache _distributedCache; /// <summary> /// 认证框架 /// </summary> private AuthenticationScheme _scheme; /// <summary> /// 认证上下文 /// </summary> private HttpContext _context; /// <summary> /// 构造函数注入 /// </summary> /// <param name="logger"></param> /// <param name="distributedCache"></param> public TokenAuthenticationHandler(ILogger<TokenAuthenticationHandler> logger, IDistributedCache distributedCache) { _logger = logger; _distributedCache = distributedCache; } /// <summary> /// /// /// </summary> /// <param name="scheme"></param> /// <param name="context"></param> /// <returns></returns> public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { _logger.LogInformation(string.Format("初始化认证scheme:{0}", scheme.DisplayName)); _scheme = scheme; _context = context; return Task.CompletedTask; } /// <summary> /// 认证过程 /// </summary> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public Task<AuthenticateResult> AuthenticateAsync() { _logger.LogInformation("开始自定义认证!"); string token = _context.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(token)) { var userObjectStr = _distributedCache.GetString(string.Format("{0}{1}", RedisConstant.USER_TOKEN_KEY,token)); if (!string.IsNullOrEmpty(userObjectStr)) { LoginUserModel loginUser = JsonConvert.DeserializeObject<LoginUserModel>(userObjectStr); ClaimsIdentity identity = new ClaimsIdentity("Ctm"); identity.AddClaims(new List<Claim>(){ new Claim(ClaimTypes.Name,loginUser.Name), new Claim(ClaimTypes.NameIdentifier,loginUser.Id.ToString()), new Claim(ClaimTypes.MobilePhone,loginUser.Mobile), new Claim(ClaimTypes.DateOfBirth,loginUser.birthday.ToShortTimeString()) }); var claimsPrincipal = new ClaimsPrincipal(identity); return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, null, _scheme.Name))); } } return Task.FromResult(AuthenticateResult.Fail("认证失败!")); } /// <summary> /// 未登录(redis的token失效或没有) /// </summary> /// <param name="properties"></param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public Task ChallengeAsync(AuthenticationProperties? properties) { //_context.Response.Redirect("/api/Login/NoLogin"); _context.Response.StatusCode = 401; return Task.CompletedTask; } /// <summary> /// 没有权限访问 /// </summary> /// <param name="properties"></param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public Task ForbidAsync(AuthenticationProperties? properties) { _context.Response.StatusCode = 403; return Task.CompletedTask; } } }
3、在登录逻辑中,验证用户账号密码,通过则生成token和查询用户信息,存入redis中,设置过期时间,并且返回token信息。
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; using System.Buffers.Text; using System.Text.Json.Serialization; namespace tqf.LoginMyTokenRedis.demo.Controllers { /// <summary> /// /// </summary> [ApiController] [Route("[controller]/[action]")] public class AccountController : ControllerBase { private readonly ILogger<WeatherForecastController> _logger; private IDistributedCache _distributedCache; /// <summary> /// /// </summary> /// <param name="logger"></param> /// <param name="distributedCache"></param> public AccountController(ILogger<WeatherForecastController> logger, IDistributedCache distributedCache) { _logger = logger; _distributedCache = distributedCache; } /// <summary> /// 登录验证接口 /// </summary> /// <param name="name"></param> /// <param name="password"></param> /// <returns></returns> [HttpPost] public string Login(string name, string password) { //账号密码验证通过 if (name.Equals(password)) { // 获取用户信息 LoginUserModel loginUserModel = new LoginUserModel(); loginUserModel.Address = "测试"; loginUserModel.Id = 1; loginUserModel.Name = "test"; loginUserModel.Mobile = "10086"; loginUserModel.birthday = DateTime.Now; // 生成token信息 string token = Base64Helper.EncodingString(string.Format("{0}{1}{2}{3}", loginUserModel.Id, loginUserModel.Name, loginUserModel.Address, loginUserModel.birthday)); // 定义滑动过期时间 DistributedCacheEntryOptions distributedCacheEntryOptions = new DistributedCacheEntryOptions(); distributedCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(60)); // redis存储token信息 _distributedCache.SetString(string.Format("{0}{1}", RedisConstant.USER_TOKEN_KEY, token), JsonConvert.SerializeObject(loginUserModel), distributedCacheEntryOptions); return token; } else { return string.Empty; } } } }
4、在接口请求的Header的Authorization中带上token信息,验证通过则在接口HttpContext.User中获取用户信息(Claims)。
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace tqf.LoginMyTokenRedis.demo.Controllers { /// <summary> /// /// </summary> [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } /// <summary> /// 权限验证 /// </summary> /// <returns></returns> [HttpGet] [Authorize] public IEnumerable<WeatherForecast> Get() { var user = HttpContext.User; return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } }
5、认证的主要参数类型Claim:相当于一个身份单元,存储着键值信息;ClaimsIdentity:身份证,身份单元的集合(可以理解为身份证上有多个身份单元);ClaimsPrincipal:身份证的载体,一个人有多重身份,那么会有多个身份证,比如既有身份证又有学生证;AuthenticateResult:认证结果;AuthenticationTicket:表示一个经过认证后颁发的证书
三、总结
上述完成自定义认证token,结合ASP.NET Core的认证服务、认证Handler,Redis缓存过期特性完成。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
2021-11-30 事件总线(EventBus)的Demo实践