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缓存过期特性完成。

posted @   tuqunfu  阅读(203)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
历史上的今天:
2021-11-30 事件总线(EventBus)的Demo实践
点击右上角即可分享
微信分享提示