前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IdentityServer4 手动验签及日志记录

IdentityServer4 手动验签及日志记录

作者头像
蓝夏
发布2019-05-15 09:52:47
8870
发布2019-05-15 09:52:47
举报
文章被收录于专栏:bluesummerbluesummer

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)
    {
       。。。。
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从HttpHeader中取出Token
  • 解析token字符串
  • 请求授权中心获取jwk配置:
  • Token签名验证
  • 授权日志
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档