asp.net core 3.1 自定义中间件实现jwt token认证

  • A+
所属分类:.NET技术
摘要

话不多讲,也不知道咋讲!直接上代码暂时是使用微软提供类库生成,如果有想法可以自己生成

话不多讲,也不知道咋讲!直接上代码

认证信息承载对象【user】

/// <summary> /// 认证用户信息 /// </summary> public class DyUser {     /// <summary>     /// 用户ID     /// </summary>     public int UserId { get; set; }     /// <summary>     /// 所属商户ID     /// </summary>     public int? TenantId { get; set; } } 

Jwt配置对象

public class AuthOptions {     /// <summary>     /// Jwt认证Key     /// </summary>     public string Security { get; set; }     /// <summary>     /// 过期时间【天】     /// </summary>     public int Expiration { get; set; } } 

JWT管理接口

public interface IAuthManage {     /// <summary>     /// 生成JwtToken     /// </summary>     /// <param name="user">用户信息</param>     /// <returns></returns>     string GenerateJwtToken(DyUser user); } 

JWT管理接口实现

暂时是使用微软提供类库生成,如果有想法可以自己生成

public class MicrosoftJwtAuthManage : IAuthManage {     private readonly AuthOptions _authOptions;     public MicrosoftJwtAuth(AuthOptions authOptions)     {         _authOptions = authOptions;     }      public string GenerateJwtToken(DyUser user)     {         var tokenHandler = new JwtSecurityTokenHandler();         var key = Encoding.ASCII.GetBytes(_authOptions.Security);         var tokenDescriptor = new SecurityTokenDescriptor         {             Subject = new ClaimsIdentity(new Claim[]             {                 new Claim("user",user.ToJson())             }),             Expires = DateTime.UtcNow.AddDays(_authOptions.Expiration),//一周过期             SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)         };         var token = tokenHandler.CreateToken(tokenDescriptor);         return tokenHandler.WriteToken(token);     } } 

处理JWT中间件

这里借鉴国外大牛的代码,主要就是验证jwt并且存把解析出来的数据存放到当前上下文

public class JwtMiddleware {     private readonly RequestDelegate _next;     private readonly AuthOptions _authOptions;      public JwtMiddleware(RequestDelegate next, AuthOptions authOptions)     {         _next = next;         _authOptions = authOptions;     }      public async Task Invoke(HttpContext context)     {         //获取上传token,可自定义扩展         var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last()                     ?? context.Request.Headers["X-Token"].FirstOrDefault()                     ?? context.Request.Query["Token"].FirstOrDefault()                     ?? context.Request.Cookies["Token"];          if (token != null)             AttachUserToContext(context, token);          await _next(context);     }      private void AttachUserToContext(HttpContext context, string token)     {         try         {             var tokenHandler = new JwtSecurityTokenHandler();             var key = Encoding.ASCII.GetBytes(_authOptions.Security);             tokenHandler.ValidateToken(token, new TokenValidationParameters             {                 ValidateIssuerSigningKey = true,                 IssuerSigningKey = new SymmetricSecurityKey(key),                 ValidateIssuer = false,                 ValidateAudience = false,                 // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)                 ClockSkew = TimeSpan.Zero             }, out SecurityToken validatedToken);              var jwtToken = (JwtSecurityToken)validatedToken;             var user = jwtToken.Claims.First(x => x.Type == "user").Value.ToJsonEntity<DyUser>();              //写入认证信息,方便业务类使用             var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim("user", jwtToken.Claims.First(x => x.Type == "user").Value) });             Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity);              // attach user to context on successful jwt validation             context.Items["User"] = user;         }         catch         {             // do nothing if jwt validation fails             // user is not attached to context so request won't have access to secure routes             throw;         }     } } 

权限过滤器

这个根据刚才中间件的存放的信息判断是否授权成功,支持匿名特性

public class ApiAuthorizeAttribute : Attribute, IAuthorizationFilter {     public void OnAuthorization(AuthorizationFilterContext context)     {         var user = context.HttpContext.Items["User"];         //验证是否需要授权和授权信息         if (HasAllowAnonymous(context) == false && user == null)         {             // not logged in             context.Result = new JsonResult(new {message = "Unauthorized"})                 {StatusCode = StatusCodes.Status401Unauthorized};         }     }      private static bool HasAllowAnonymous(AuthorizationFilterContext context)     {         var filters = context.Filters;         if (filters.OfType<IAllowAnonymousFilter>().Any())         {             return true;         }          // When doing endpoint routing, MVC does not add AllowAnonymousFilters for AllowAnonymousAttributes that         // were discovered on controllers and actions. To maintain compat with 2.x,         // we'll check for the presence of IAllowAnonymous in endpoint metadata.         var endpoint = context.HttpContext.GetEndpoint();         return endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null;     } } 

扩展IServiceCollection

方便以后管理和维护,主要就是把需要的对象注入到IOC容器里面

public static class AuthServiceExtensions {     public static void AddAuth(this IServiceCollection services, Action<AuthOptions> configAction)     {         var options = new AuthOptions();         configAction(options);         services.AddSingleton(options);         services.AddSingleton<IAuthManage>(new MicrosoftJwtAuthManage(options));     } } 

NullDySession

这里是为了在非控制器类获取用户信息用

/// <summary> /// 当前会话对象 /// </summary> public class NullDySession {     /// <summary>     /// 获取DySession实例     /// </summary>     public static NullDySession Instance { get; } = new NullDySession();     /// <summary>     /// 获取当前用户信息     /// </summary>     public DyUser DyUser     {         get         {             var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;              var claimsIdentity = claimsPrincipal?.Identity as ClaimsIdentity;              var userClaim = claimsIdentity?.Claims.FirstOrDefault(c => c.Type == "user");             if (userClaim == null || string.IsNullOrEmpty(userClaim.Value))             {                 return null;             }              return userClaim.Value.ToJsonEntity<DyUser>();         }     }      private NullDySession()     {     } } 

到这为止准备工作完成,开始用起来吧~

修改【Startup.cs->ConfigureServices】

//添加全局权限认证过滤器 services.AddControllersWithViews(options => { 	options.Filters.Add<ApiAuthorizeAttribute>(); }) //添加认证配置信息 services.AddAuth(options => { 	options.Expiration = 7;//天为单位 	options.Security = apolloConfig.Get("JwtSecret"); }); 

添加中间件【Startup.cs->Configure(IApplicationBuilder app, IWebHostEnvironment env)方法中】

注意中间件的位置

//启用jwt认证中间件 app.UseMiddleware<JwtMiddleware>(); 

api使用案例【使用构造注入IAuthManage】

//生成了JwtToken var newToken = _authManage.CreateJwtToken(para.Sn);  //Controller里面获取用户信息 public DyUser DyUser => (DyUser)this.HttpContext.Items["User"];  //普通class类获取用户信息【如果不是Web应用,需要独立引用Dymg.Core】 NullDySession.Instance.DyUser.UserId;  //如果个别不接口不需要认证,可以使用AllowAnonymous特性 [HttpPost, AllowAnonymous] public string Noauth() { 	return "这个不需要授权"; } 

前端调用案例

//token放在请求头里面 Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y //自定义key x-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y  //使用连接字符串方式 https://xxxxx/user/getUser?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y