net core SSO 单点登录和控制器中获取Token和UserId
控制器中注入
[ApiController] //[Authorize] [ServiceFilter(typeof(LDAPPLoginFilter))] [Route("/file/api/[controller]/[action]")] public class BaseController : ControllerBase { public ITokenHelper _tokenHelper; public IHttpContextAccessor _httpContext; /// <summary> /// 当前用户ID /// </summary> public string CurrentUserId { get{ var userId = "00185770cfb24ccca22e14f8b9111111"; if (_httpContext != null && _tokenHelper != null) { var tokenobj = _httpContext.HttpContext.Request.Headers["Authorization"].ToString(); //读取配置文件中 的 秘钥 var secretKey = ConfigurationManager.JwtTokenConfig["Secret"]; string token = tokenobj.Split(" ")[1].ToString();//剔除Bearer string mobile = "";//用户手机号 //验证jwt,同时取出来jwt里边的用户ID TokenType tokenType = _tokenHelper.ValiTokenState(token, secretKey , a => a["iss"] == "test.cn" && a["aud"] == "test" , action => { userId = action["id"]; mobile = action["phone_number"]; }); } return userId; } } } public FileServerController(ITokenHelper tokenHelper, IHttpContextAccessor httpContextAccessor) { _tokenHelper = tokenHelper; _httpContext = httpContextAccessor; }
调用
登录过滤器
/// <summary> /// 用户登录过滤器 /// 需要登录时 Check请求头中的token字段 /// </summary> public class LDAPPLoginFilter : Attribute, IActionFilter { private readonly ITokenHelper _tokenHelper; /// <summary> /// 通过依赖注入得到数据访问层实例 /// </summary> /// <param name="tokenHelper"></param> public LDAPPLoginFilter(ITokenHelper tokenHelper) { _tokenHelper = tokenHelper; } public void OnActionExecuted(ActionExecutedContext context) { } /// <summary> /// 操作过滤器 /// </summary> /// <param name="context">请求上下文</param> /// <param name="next">下一个过滤器或者终结点本身</param> /// <returns></returns> //async public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) //{ // var descriptor = context.ActionDescriptor; // // 当未标记为 AllowAnonymous 时再执行 // if (!descriptor.EndpointMetadata.Any(p => p is IAllowAnonymous)) // { // context.HttpContext.Request.Headers.TryGetValue("token", out var tokens); // var token = tokens.FirstOrDefault(); // if (string.IsNullOrWhiteSpace(token)) // { // //如果没有token 直接返回 // context.Result = new UnauthorizedResult();//直接返回401 统一请求返回的话,这里修改成统一需要登录的请求实体 // } // else // { // var redisKey = $"_APP_Token_{token}"; // //存在token // var userInfo = RedisClient.GetValue<CommonUserModel>(redisKey); // if (userInfo == null) // { // context.Result = new UnauthorizedResult();//直接返回401 统一请求返回的话,这里修改成统一需要登录的请求实体 // return; // } // // RedisClient.SetValue(redisKey, userInfo, 180 * 24 * 60);//180天内登录一次就重新置成180天 暂时不启用,重复写入性能影响大,一个月写入一次。 写入缓存时需要设计 cachetime // await next(); // } // } //} /// <summary> /// 请求接口时进行拦截处理 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { //LawcaseEvidenceFilePreview if (context.ActionDescriptor.EndpointMetadata.Any(it => it.GetType() == typeof(NoLDAPPLoginFilter))) { return; } //Action 名称过滤 if (context.ActionDescriptor.DisplayName.Contains("ActionName")) { return; } //var ret = new Models.Commons.ResultInfoModel(); var ret = new AjaxResult(); try { //获取请求头中的Token var tokenobj = context.HttpContext.Request.Headers["Authorization"].ToString(); if (string.IsNullOrEmpty(tokenobj)) { ret.state = (int)ResultCodeEnum.ApiUnauthorized; ret.message = "接口未授权"; context.Result = new JsonResult(ret); return; } //读取配置文件中 的 秘钥 var secretKey = ConfigurationManager.JwtTokenConfig["Secret"]; string token = tokenobj.Split(" ")[1].ToString();//剔除Bearer string userId = string.Empty; string mobile = string.Empty;//用户手机号 //var token = getToken(context); //验证jwt,同时取出来jwt里边的用户ID TokenType tokenType = _tokenHelper.ValiTokenState(token, secretKey , a => a["iss"] == "test.cn" && a["aud"] == "test" , action => { userId = action["id"]; mobile = action["phone_number"]; }); if (tokenType == TokenType.FormError) { ret.state = (int)ResultCodeEnum.ApiUnauthorized; ret.message = "登录失效,请重新登录!";//token非法 context.Result = new JsonResult(ret); return; } if (tokenType == TokenType.Fail) { ret.state = (int)ResultCodeEnum.ApiUnauthorized; ret.message = "用户信息验证失败!";//token验证失败 context.Result = new JsonResult(ret); return; } if (tokenType == TokenType.Expired) { ret.state = (int)ResultCodeEnum.ApiUnauthorized; ret.message = "登录失效,请重新登录!"; context.Result = new JsonResult(ret); return; } if (string.IsNullOrEmpty(userId)) { //获取用户编号失败时,阻止用户继续访问接口 ret.state = (int)ResultCodeEnum.Error; ret.message = "用户信息丢失"; context.Result = new JsonResult(ret); return; } //自定义代码逻辑, 取出token中的 用户编号 进行 用户合法性验证即可 //。。。。。。。 } catch (Exception ex) { ret.state = (int)ResultCodeEnum.Error; ret.message = "请求来源非法" + ex.Message.ToString(); context.Result = new JsonResult(ret); return; } } }
/// <summary> /// /// </summary> public interface ITokenHelper { /// <summary> /// Token验证 /// </summary> /// <param name="encodeJwt">token</param> /// <param name="secretKey">secretKey</param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param> /// <returns></returns> bool ValiToken(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad = null); /// <summary> /// 带返回状态的Token验证 /// </summary> /// <param name="encodeJwt">token</param> /// <param name="secretKey">secretKey</param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param> /// <param name="action"></param> /// <returns></returns> TokenType ValiTokenState(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action); }
/// <summary> /// /// </summary> public class TokenHelper : ITokenHelper { /// <summary> /// 验证身份 验证签名的有效性 /// </summary> /// <param name="encodeJwt"></param> /// <param name="secretKey">配置文件中取出来的签名秘钥</param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param> public bool ValiToken(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad = null) { var success = true; var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass return false; var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); //配置文件中取出来的签名秘钥 var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) return success;//签名不正确直接返回 //其次验证是否在有效期内(也应该必须) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) return true; //再其次 进行自定义的验证 success = success && validatePayLoad(payLoad); return success; } /// <summary> /// 时间转换 /// </summary> /// <param name="date"></param> /// <returns></returns> private long ToUnixEpochDate(DateTime date) { return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds); } /// <summary> /// /// </summary> /// <param name="encodeJwt"></param> /// <param name="secretKey"></param> /// <param name="validatePayLoad"></param> /// <param name="action"></param> /// <returns></returns> public TokenType ValiTokenState(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action) { //iss: jwt签发者 //sub: jwt所面向的用户 //aud: 接收jwt的一方 //exp: jwt的过期时间,这个过期时间必须要大于签发时间 //nbf: 定义在什么时间之前,该jwt都是不可用的 //iat: jwt的签发时间 //jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击 var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass return TokenType.FormError; //var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))))) return TokenType.FormError; var now = ToUnixEpochDate(DateTime.UtcNow); var nbf = long.Parse(payLoad["nbf"].ToString()); var exp = long.Parse(payLoad["exp"].ToString()); if (!(now >= nbf && now < exp)) { action(payLoad); return TokenType.Expired; } //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) { action(payLoad); return TokenType.Ok; } //再其次 进行自定义的验证 if (!validatePayLoad(payLoad)) return TokenType.Fail; //可能需要获取jwt摘要里边的数据,封装一下方便使用 action(payLoad); return TokenType.Ok; } }
public class TokenManagement { public string Secret { get; set; } public string Issuer { get; set; } public string Audience { get; set; } public int AccessExpiration { get; set; } public int RefreshExpiration { get; set; } }
public class NoLDAPPLoginFilter : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { //var ret = new Models.Commons.ResultInfoModel(); //ret.Head.ErrorCode = 1000; //ret.Head.Msg = "成功!"; //context.Result = new JsonResult(ret); } }
/// <summary> /// 设置该方法不会进行AES加密和解密操作,直接传入参数和响应结果 /// </summary> [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] public class NoAESMiddlewareAttribute : Attribute { }
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; LD.Code.ConfigurationManager.Configure(Configuration); //注册日志功能 LD.Code.LogFactory.ResisterLogger(); } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //services.AddSwaggerGen(options => //{ // #region 文档格式化 // options.SwaggerDoc("v1", new OpenApiInfo // { // Version = "V1", // Title = "ASP.NET CORE WepbApi 3.1", // Description = "基于Asp.Net Core 3.1 实现文件上传下载", // Contact = new OpenApiContact // { // Name = "律盾", // Email = "lvduntech@lvdun.com" // }, // License = new OpenApiLicense // { // Name = "许可证", // } // }); // options.DocumentFilter<HiddenApiFilter>(); // #endregion //}); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "XXX服务接口", Version = "v1" }); // 获取xml文件名 var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; // 获取xml文件路径 var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); // 添加控制器层注释,true表示显示控制器注释 c.IncludeXmlComments(xmlPath, true); ////LD.Domain.xml //xmlFile = "LD.Domain.xml"; //xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); //c.IncludeXmlComments(xmlPath, true); ////LD.Code.xml //xmlFile = "LD.Code.xml"; //xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); //c.IncludeXmlComments(xmlPath, true); c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); c.DocumentFilter<HiddenApiFilter>(); }); services.Configure<TokenManagement>(Configuration.GetSection("JwtTokenConfig")); var token = Configuration.GetSection("JwtTokenConfig").Get<TokenManagement>(); services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(x => { x.RequireHttpsMetadata = false; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)), ValidIssuer = token.Issuer, ValidAudience = token.Audience, ValidateIssuer = false, ValidateAudience = false }; }); LD.Services.RegisterIoc.Register(services); // ActionExecutingContext services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddTransient<ITokenHelper, TokenHelper>(); services.AddScoped<NoLDAPPLoginFilter>(); services.AddScoped<LDAPPLoginFilter>(); services.AddControllers(); //跨域 var corsstring = Configuration.GetSection("Cors").Value; string[] corsarray = corsstring.Split(','); services.AddCors(options => options.AddPolicy("CorsPolicy", builder => { builder.AllowAnyMethod().AllowAnyHeader() .WithOrigins(corsarray) .AllowCredentials(); })); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} DocExpansion swaggeerDoc; //if (env.IsDevelopment()) //{ app.UseDeveloperExceptionPage(); swaggeerDoc = DocExpansion.List; //添加Swagger有关中间件 app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "FileServerAPI v1"); c.RoutePrefix = string.Empty; c.DocExpansion(DocExpansion.None); }); //} //else //{ // swaggeerDoc = DocExpansion.None; //} app.UseStaticFiles(); //app.UseStaticFiles(new StaticFileOptions //{ //设置不限制content-type // ServeUnknownFileTypes = true //}); app.UseHttpsRedirection(); app.UseRouting();//1.路由 app.UseCors("CorsPolicy"); app.UseAuthentication();//2.认证 app.UseAuthorization();//3.授权 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); //app.UseSwagger(); //app.UseSwaggerUI(c => //{ // c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1"); //}); } }
public class Enum { /// <summary> /// 系统数据返回状态 /// </summary> public enum ResultCodeEnum { /// <summary> /// 失败 /// </summary> [Description("失败")] Error = 0, /// <summary> /// 成功 /// </summary> [Description("成功")] Success = 1, /// <summary> /// 接口未授权 /// </summary> [Description("接口未授权")] ApiUnauthorized = 401 } /// <summary> /// /// </summary> public enum TokenType { /// <summary> /// 验证成功 /// </summary> [Description("验证成功")] Ok, /// <summary> /// 验证失败 /// </summary> [Description("验证失败")] Fail, /// <summary> /// Token失效 /// </summary> [Description("Token失效")] Expired, /// <summary> /// Token非法 /// </summary> [Description("Token非法")] FormError } }