IdentityServer4 手動驗簽及日誌記錄
阿新 • • 發佈:2019-05-10
interact sse tail 記錄 times 介紹 pad 。。 seconds
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 手動驗簽及日誌記錄