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)
{
。。。。
}
}

IdentityServer4 手动验签及日志记录的更多相关文章

  1. IdentityServer4之JWT签名(RSA加密证书)及验签

    一.前言 在IdentityServer4中有两种令牌,一个是JWT和Reference Token,在IDS4中默认用的是JWT,那么这两者有什么区别呢? 二.JWT与Reference Token ...

  2. 第十一篇 Integration Services:日志记录

    本篇文章是Integration Services系列的第十一篇,详细内容请参考原文. 简介在前一篇,我们讨论了事件行为.我们分享了操纵事件冒泡默认行为的方法,介绍了父子模式.在这一篇,我们会配置SS ...

  3. java安全入门篇之接口验签

    文章大纲 一.加密与验签介绍二.接口验签实操三.项目源码下载   一.加密与验签介绍   大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被 ...

  4. 巧用CurrentThread.Name来统一标识日志记录

    ▄︻┻┳═一Agenda: ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录 ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录(续) ▄︻┻┳═一巧用Cur ...

  5. 【译】第十一篇 Integration Services:日志记录

    本篇文章是Integration Services系列的第十一篇,详细内容请参考原文. 简介在前一篇,我们讨论了事件行为.我们分享了操纵事件冒泡默认行为的方法,介绍了父子模式.在这一篇,我们会配置SS ...

  6. jmeter接口测试-调用java的jar包-csv参数化请求-BeanShellPreProcessor生成验签作为请求验证参数-中文乱码----实战

    背景及思路: 需求:要做 创建新卡 接口的测试,要求: 1. 不需要每次手动修改请求参数. 方案:文中先用excle将数据准备好,导出为csv格式,再用jmeter的csv请求进行参数化 2. 卡号需 ...

  7. NodeJs支付宝移动支付签名及验签

    非常感谢 :http://www.jianshu.com/p/8513e995ff3a?utm_campaign=hugo&utm_medium=reader_share&utm_co ...

  8. 从零开始编写自己的C#框架(20)——框架异常处理及日志记录

    最近很忙,杂事也多,所以开发本框架也是断断续续的,终于在前两天将前面设定的功能都基本完成了,剩下一些小功能遗漏的以后发现再补上.接下来的章节主要都是讲解在本框架的基础上进行开发的小巧. 本框架主要有四 ...

  9. openssl+前端jsrsa签名+后端nodejs验签

    内容如标题所示,总体分为三个部分: 一.win10下安装openssl,然后通过openssl工具生成RSA的公钥和私钥 (1)win10下安装openssl需要的工具有:VS2013,Perl,na ...

随机推荐

  1. 【纸模】六角大王 Super 5.6 CHS 简体中文版 U20080725+[手册]窗口与工具的概要(PDF格式)

    六角大王5.6简体中文版中文化:star21 主界面<ignore_js_op> 人体生成模式<ignore_js_op> 动画<ignore_js_op> < ...

  2. scrapy 一些坑

    scrapy爬虫出现Forbidden by robots.txt # Obey robots.txt rulesROBOTSTXT_OBEY = False scrapy定时执行抓取任务 用cron ...

  3. Python classes to extract information from the Linux kernel /proc files.

    python/python-linux-procfs/python-linux-procfs.git - Python classes to extract information from the ...

  4. Vue实现图片预加载

    <script>export default { data () { return { count: 0, } }, mounted: function() { this.preload( ...

  5. APP:目录

    ylbtech-APP:目录 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部     7.返回顶部   8.返回顶部   9.返回顶部   ...

  6. python接入微博第三方API之1环境准备

    环境准备: 1.注册微博账号 2.注册应用

  7. sqllite connectionstring setting

    https://www.connectionstrings.com/sqlite/ SQLite.NET Basic Data Source=c:\mydb.db;Version=3; Version ...

  8. visual studio code跳转到定义处插件

    visual studio code 中使用跳转到定义处的插件 https://marketplace.visualstudio.com/items?itemName=Shan.code-settin ...

  9. 【PHP】php7.2报错The each() function is deprecated. This message will be suppressed on furthe

    php7.2以上 废除了 each()方法,项目中用到的地方会出现以下报错 The each() function is deprecated. This message will be suppre ...

  10. sklearn使用高斯核SVM显示支持向量

    import graphviz import mglearn from mpl_toolkits.mplot3d import Axes3D from sklearn.datasets import ...