net core SSO 单点登录和控制器中获取Token和UserId

7 篇文章 0 订阅

net core SSO 单点登录和控制器中获取Token和UserId

在写WebApi时常常是要获取登录用户的oken和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
        }
    }

END

  • 12
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

StevenChen85

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值