zoukankan      html  css  js  c++  java
  • IdentityServer4 手动验签及日志记录

    IdentityServer4的基础知识和使用方式网上有很多特别优秀的文章,如果有对其不了解的推荐阅读一下下面的两篇文章

    http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

    https://www.cnblogs.com/stulzq/p/8119928.html

    当然如果你英文可以的话,官方文档还是要读上一读的。

    这篇文章主要介绍一下手动实现Api的token校验,及认证授权过程中相关的日志记录

    如果是在.net core的api中,token校验的实现方式是相当简单的:

     services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
    options.Authority = "http://testlocal.com:56428";
    options.RequireHttpsMetadata = false;
    options.Audience = "api1";
    options.Events = new MyJwtBearerEvents();
    });
    

    可以同过实现JwtBearerEvents接口,来记录Token校验过程的相关日志。Token校验失败api返回401。

    但是如果不想要返回401呢,或者在是.net framework中同样使用IdentityServer4,就需要我们手动实现token的校验

    从HttpHeader中取出Token

    net FrameWork

     if (header.Authorization == null || header.Authorization.Parameter == null)
            {
                return new ValidTokenResult(false, "not exit token");
            }
            string tokenStr = header.Authorization.Parameter;
    

    net Core

      if (header == null || !header.ContainsKey("Authorization"))
                result = new OpenApiResponse(CodeEnum.NotExistToken, "not exit token");
            else
                string tokenStr = header["Authorization"];
    

    解析token字符串

     internal TokenModel GetTokenModel(string jwttoken)
        {
            string[] arrys = jwttoken.Split('.');
            try
            {
                string headstr = Base64Helper.DecodeBase64Url(arrys[0]);
                string paylodstr = Base64Helper.DecodeBase64Url(arrys[1]);
                TokenHeader head = JsonHelper.DeserializeObject<TokenHeader>(headstr);
                TokenPayload paylod = JsonHelper.DeserializeObject<TokenPayload>(paylodstr);
                return new TokenModel() { Header = head, Plyload = paylod, TokenRaw = jwttoken, secred = arrys[2] };
            }
            catch (Exception ex)
            {
                ToolFactory.LogHelper.Error("解析tokenHead报错,jwttoken:" + jwttoken, ex);
                throw;
            }
        }
    

    请求授权中心获取jwk配置:

    .获取token配置:授权地址+.well-known/openid-configuration
    .获取token配置:根据上一步返回的jwks_uri,请求:jwks_uri,返回的结果如下:

    {
    "keys": [
        {
            "kty": "RSA",
            "use": "sig",
            "kid": "237271f420de7fdd3736231f59890a79",
            "e": "AQAB",
            "n": "vos7SOZyO5fZu9o8RVGpsOaIHXXCluky7hSWxSYTZvIl5QkjV3k15O1k6mtidVv0KmNdBBeFvo0aijHr6M93Xe-3NLIqyQTuXLIjHNJd4VdJXkzsA5jo3ScVgIhKJwTvd0Lu7eLAWRj8ArgWaPrizfuuP6zw20vzr_cdiz6CQIJ6FmWKI5LAAI2tPr6y08Ekb0B6BKtifGPL6q0cVHo_U9mNCBjITwwl8fF-denix4RXULwWJJD19VBQAQZdZSxeXjhYCW4GnkRHtSmwabaS1qihp6GvrC0ch5d3MZZiqi7imX0R7dOdF9Jdl-vl7oe98G79DzsunystV6nElndenw",
            "alg": "RS256"
        }
    ]
    }
    

    Token签名验证

    • 验证header中的kid和jwk中的kid是否匹配

        	//调用接口获取jwk的相关信息,jwk包括公钥等用于验签token的信息
            var jwk = await GetCacheJwkConfig().ConfigureAwait(false);
            var defaultkey = jwk.keys.Where(t => t.kid == tokenModel.Header.kid).FirstOrDefault();
            if (defaultkey == null)
            {
                return new ValidTokenResult(false, "token valid kid err");
            }
      
    • RSA验证签名。授权中心用私钥签名、我们客户端用公钥验签

            var signValid = ValidateJwtTokenSigned(token, defaultkey.e, defaultkey.n);
            if (!signValid.Success)
            {
                signValid.Message = "token valid sign err " + signValid.Message;
                return signValid;
            }
        	
        	public ValidTokenResult ValidateJwtTokenSigned(string token, string exponent, string modulus)
            {
                try
                {
                    string[] arrs = token.Split('.');
                    string payload = arrs[0] + "." + arrs[1];
                    byte[] encodedBytes = Encoding.UTF8.GetBytes(payload);
                    byte[] singbytes = Base64Helper.DecodeBase64UrlToByte(arrs[2]);
      
                    RSAParameters param = new RSAParameters()
                    {
                        Exponent = Base64Helper.DecodeBase64UrlToByte(exponent),
                        Modulus = Base64Helper.DecodeBase64UrlToByte(modulus)
                    };
                    using (RSACryptoServiceProvider _rsa = new RSACryptoServiceProvider())
                    {
                        _rsa.ImportParameters(param);
                        bool result = _rsa.VerifyData(encodedBytes, SHA256.Create(), singbytes);
                        return new ValidTokenResult(result, "");
                    }
                }
                catch (Exception ex)
                {
                    ToolFactory.LogHelper.Error("token验证签名出错,jwttoken:" + token, ex);
                    return new ValidTokenResult(false, ex.Message);
                }
            }
      

    至此,一个最基础的Token校验就完成了,当然后面仍需要判断token的超时时间及权限等信息

    为了防止网络耗时引起的时间误差,我预留了30秒的时间

                 DateTime dtstart = TimeHelper.ConvertLongToDateTime(tokenModel.Plyload.nbf).AddSeconds(-30);
                if (dtstart > DateTime.Now)
                {
                    return new ValidTokenResult(false, "token nbf time err"+ tokenModel.Plyload.nbf);
                }
                DateTime dtend = TimeHelper.ConvertLongToDateTime(tokenModel.Plyload.exp).AddSeconds(30);
                if (dtend < DateTime.Now)
                {
                    return new ValidTokenResult(false, "token is timeout" + tokenModel.Plyload.exp);
                }
                if (!tokenModel.Plyload.scope.Contains(_options.Audience))
                {
                    return new ValidTokenResult(false, "token has no permission for this api");
                }
    

    授权日志

    授权的日志可通过实现IEventSink监听相关事件,需要设置相关的Eventsoptions为true

     		services.TryAddTransient<IEventSink, Auth.SeqEventSink>();
    
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
    
            services.AddIdentityServer(o =>
            {
                o.Caching.ClientStoreExpiration = new TimeSpan(0, 0, 50);
                o.UserInteraction.LoginUrl = "/IdSerAccount/Login";  //授权登陆界面
                o.UserInteraction.ConsentUrl = "/IdSerConsent/Index"; //授权确认界面
                o.UserInteraction.LogoutUrl = "/IdSerAccount/Logout"; //授权登出界面
                o.Events.RaiseSuccessEvents = true;
                o.Events.RaiseFailureEvents = true;
                o.Events.RaiseErrorEvents = true;
    
            })
    

    EventSink:

     public class SeqEventSink : IEventSink
    {
        public Task PersistAsync(Event evt)
        {
            return Task.Run(() =>
            {
                try
                {
                    //登陆登出的日志忽略
                    if (evt.Id == EventIds.UserLoginSuccess || evt.Id == EventIds.UserLogoutSuccess)
                        return;
                    BIdentityEventLog iel = new BIdentityEventLog()
                    {
                        IEL_CREATION_DT = DateTime.Now,
                        IEL_EVENTY_TYPE = evt.EventType.ToString(),
                        IEL_EVENT_NAME = evt.Name,
                        IEL_EVENT_ID = evt.Id,
                        IEL_TIMESTAMP = evt.TimeStamp, 
                        IEL_REMOTEIP_ADDRESS = evt.RemoteIpAddress,
                        IEL_CATEGORY = evt.Category
                    };
                    if (evt is ApiAuthenticationSuccessEvent)
                    {
                        var newevt = (evt as ClientAuthenticationFailureEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                    }
                    else if (evt is ClientAuthenticationSuccessEvent)
                    {
                        var newevt = (evt as ClientAuthenticationSuccessEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                    }
                    else if (evt is ConsentGrantedEvent)
                    {
                        var newevt = (evt as ConsentGrantedEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                    }
                    else if (evt is InvalidClientConfigurationEvent)
                    {
                        var newevt = (evt as InvalidClientConfigurationEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                    }
                    else if (evt is TokenIssuedFailureEvent)
                    {
                        var newevt = (evt as TokenIssuedFailureEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                        iel.IEL_ERROR = newevt.Error;
                        iel.IEL_END_POINT = newevt.Endpoint;
                    }
                    else if (evt is TokenIssuedSuccessEvent)
                    {
                        var newevt = (evt as TokenIssuedSuccessEvent);
                        iel.IEL_CLIENT_ID = newevt.ClientId;
                        iel.IEL_END_POINT = newevt.Endpoint;
                    }
                    int ielId = AddIel(iel);
                    var jsonData = JsonConvert.SerializeObject(evt);
                    AddXie(new XIdentityEventLog() { XIE_IEL_ID = ielId, XIE_JSON = jsonData });
                }
                catch (Exception ex)
                {
                    LogHelper.Error("授权事件记录失败:NAME" + evt.Name, ex);
                    LogHelper.Error("授权事件记录失败,{Name}, Details: {@details}", evt.Name, evt);
                }
            });
        }
    
        private int AddIel(BIdentityEventLog model)
        {
           。。。。
        }
        private int AddXie(XIdentityEventLog model)
        {
           。。。。
        }
    }
  • 相关阅读:
    Linux用户配置文件、口令配置文件、组配置文件
    Linux忘记Root密码怎么找回
    Linux运行级别及解释
    Maven获取resources的文件路径、读取resources的文件
    常见状态码100、200、300、400、500等
    JVM内存模型
    tcl使用小结
    MFQ&&PPDCS
    总结下自己在工作中有关联的TCP/IP协议
    二层交换机和三层交换机
  • 原文地址:https://www.cnblogs.com/bluesummer/p/10843697.html
Copyright © 2011-2022 走看看