最近公司采用asp.net core的站點在外測環境中,總是發現存在session丟失的情況。排查了好久,客戶端.AspNetCore.Session的cookie未丟失,session的分布式緩存采用的redis主從復制也未發現問題,也想用cookie的變通解決方案,但是沒解決根本問題,總是覺得如魚梗在喉的不爽。后來在排查的過程中,發現同一個客戶端通過nginx居然有時候會負載到不同的網站服務器上,檢查過nginx,是通過ip_hash進行轉發的啊,迷惑不解之際,公司的運維一語破的,原來公司是采用雙線路由器的。於是猜測,是否因為存儲cookie加密的key存在不同服務器上所導致。
打開微軟的Session的源碼,先查看SessionMiddleware中間件的代碼,其中有以下的關鍵源碼:
var cookieValue = context.Request.Cookies[_options.CookieName]; var sessionKey = CookieProtection.Unprotect(_dataProtector, cookieValue, _logger); if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength) { // No valid cookie, new session. var guidBytes = new byte[16]; CryptoRandom.GetBytes(guidBytes); sessionKey = new Guid(guidBytes).ToString(); cookieValue = CookieProtection.Protect(_dataProtector, sessionKey); var establisher = new SessionEstablisher(context, cookieValue, _options); tryEstablishSession = establisher.TryEstablishSession; isNewSessionKey = true; }
sessionKey是從客戶端的cookie中解密出來的,其中CookieProtection的_dataProtector來自
_dataProtector = dataProtectionProvider.CreateProtector(nameof(SessionMiddleware));
將系統默認注入的IDataProtectionProvider扒出來:
發現IDataProtectionProvider為Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider,該IDataProtectionProvider的keyManager為
Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager。
果然不出所料,key存在單機上,打開微軟的DataProtection-dev源碼查看FileSystemXmlRepository中獲取Key存儲路徑的實現:
眼尖的發現項目下面就有一個將key持久化到redis的實現:
添加相應的nuget包,修改ConfigureServices方法:
services.AddDataProtection()
.PersistKeysToRedis(ConnectionMultiplexer.Connect(redisConnection), dataProtectionKey);
終於完美解決!在采用分布式存儲session的時候,最好將dataProtectionProvider的key也進行共享,否則如果做了ip_hash負載均衡,客戶端ip一變,可能負載到另外一台服務器,導致存儲session的cookie數據解密不出來從而獲取不到session!