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)
{
。。。。
}
}
IdentityServer4 手动验签及日志记录的更多相关文章
- IdentityServer4之JWT签名(RSA加密证书)及验签
一.前言 在IdentityServer4中有两种令牌,一个是JWT和Reference Token,在IDS4中默认用的是JWT,那么这两者有什么区别呢? 二.JWT与Reference Token ...
- 第十一篇 Integration Services:日志记录
本篇文章是Integration Services系列的第十一篇,详细内容请参考原文. 简介在前一篇,我们讨论了事件行为.我们分享了操纵事件冒泡默认行为的方法,介绍了父子模式.在这一篇,我们会配置SS ...
- java安全入门篇之接口验签
文章大纲 一.加密与验签介绍二.接口验签实操三.项目源码下载 一.加密与验签介绍 大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被 ...
- 巧用CurrentThread.Name来统一标识日志记录
▄︻┻┳═一Agenda: ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录 ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录(续) ▄︻┻┳═一巧用Cur ...
- 【译】第十一篇 Integration Services:日志记录
本篇文章是Integration Services系列的第十一篇,详细内容请参考原文. 简介在前一篇,我们讨论了事件行为.我们分享了操纵事件冒泡默认行为的方法,介绍了父子模式.在这一篇,我们会配置SS ...
- jmeter接口测试-调用java的jar包-csv参数化请求-BeanShellPreProcessor生成验签作为请求验证参数-中文乱码----实战
背景及思路: 需求:要做 创建新卡 接口的测试,要求: 1. 不需要每次手动修改请求参数. 方案:文中先用excle将数据准备好,导出为csv格式,再用jmeter的csv请求进行参数化 2. 卡号需 ...
- NodeJs支付宝移动支付签名及验签
非常感谢 :http://www.jianshu.com/p/8513e995ff3a?utm_campaign=hugo&utm_medium=reader_share&utm_co ...
- 从零开始编写自己的C#框架(20)——框架异常处理及日志记录
最近很忙,杂事也多,所以开发本框架也是断断续续的,终于在前两天将前面设定的功能都基本完成了,剩下一些小功能遗漏的以后发现再补上.接下来的章节主要都是讲解在本框架的基础上进行开发的小巧. 本框架主要有四 ...
- openssl+前端jsrsa签名+后端nodejs验签
内容如标题所示,总体分为三个部分: 一.win10下安装openssl,然后通过openssl工具生成RSA的公钥和私钥 (1)win10下安装openssl需要的工具有:VS2013,Perl,na ...
随机推荐
- html5的datalist元素详解
html5的datalist元素详解 一.总结 一句话总结: datalist元素配合input元素可以出现有提示选择作用的选框效果,还是相对比较简便好用的 1.optgroup元素是干嘛的? opt ...
- jsch连接sftp后连接未释放掉问题排查
项目中通过jsch中的sftp实现上传下载文件.在压测过程中,由于调用到sftp,下载文件不存在时,系统不断抛出异常,内存飙升,逐渐把swap区也占满,通过top监控未发现占用内存的进程,通过查找ss ...
- xhr.js:108 Refused to set unsafe header "Cookie"
https://stackoverflow.com/questions/7210507/ajax-post-error-refused-to-set-unsafe-header-connection/ ...
- Microservices in action: java(spring) and .net
Manning | Homehttps://www.manning.com/ What is a Microservice? | Manninghttps://freecontent.manning. ...
- Flutter Drawer 侧边栏、以及侧边栏内 容布局
Flutter Drawer 侧边栏 在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边 栏.侧边栏默认是隐藏的,我们可以通过手指滑动显示 ...
- openresty开发系列19--lua的table操作
openresty开发系列19--lua的table操作 Lua中table内部实际采用哈希表和数组分别保存键值对.普通值:下标从1开始 不推荐混合使用这两种赋值方式. local color={fi ...
- bat函数调用 带返回值
bat 脚本之 使用函数 摘自:https://blog.csdn.net/peng_cao/article/details/73999076 综述 bat函数写法 bat函数调用 bat函数返回值 ...
- Qt编写自定义控件67-通用无边框
一.前言 在之前的一篇文章中写过一个通用的移动控件,作用就是用来传入任意的widget控件,可以在父类容器中自由移动.本篇文章要写的是一个通用的无边框类,确切的说这不叫控件应该叫组件才对,控件是要看得 ...
- Unity 3D网络游戏实战 pdf
Unity 3D网络游戏实战(全) 目录: 掌握Unity3D基本元素 1.1 简单的游戏 1.1.1在场景中创建一个立方体 1.1.2编写可以使立方体运动的程序 1.1.3测试游戏1.1.4总结1. ...
- Spring MVC 数据模型与视图
从控制器获取数据后,会装载数据到数据模型和视图中,然后将视图名称转发到视图解析器中,通过解析器解析后得到最终视图,最后将数据模型渲染到视图中,展示最终的结果给用户. 用ModelAndView来定 ...