零、背景介紹web
在學習ASP.NET CORE開發的過程當中,身份認證是必須考慮的一項必要的組件。ASP.NET CORE Identity是由微軟官方開發的一整套身份認證組件,兼具完整性和自由度。Docker做爲目前虛擬化的主流解決方案,能夠很快捷地實現應用的打包和部署。Nginx做爲反向代理,結合Docker多環境部署,能夠實現負載均衡功能。而在分佈式環境下,Session的共享,特別是登陸狀態的共享是難以逾越的一個「小」問題。redis
然而,這個「小」問題,卻讓我花費了大量的時間搞清楚了相互之間的協做關係,併成功實現了Docker+Nginx+Redis多種組件相結合的解決方案。docker
環境:ASP.NET Core 2.0數據庫
1、ASP.NET CORE Identityjson
爲了實現Session共享,須要在Cookie中存儲Session的ID信息以及用戶信息,從而實如今多個應用之間的信息共享。有關ASP.NET CORE Identity的介紹,這裏不在贅述。session
ASP.NET CORE Identity主要包括UserManager和SignInManager兩個主要的管理類,從名稱能夠看出來SignInManager實現的是登錄的管理,由於涉及到登陸狀態以及登陸用戶信息的共享,因此咱們須要實現自定義的SignInManager類,重寫其中最爲重要的登陸和登出方法。app
1 public override Task<SignInResult> PasswordSignInAsync(ApplicationUser user, string password, bool isPersistent, bool lockoutOnFailure) 2 { 3 return base.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure) 4 .ContinueWith<SignInResult>(task => 5 { 6 if (task.Result == SignInResult.Success) 7 { 8 LoginSucceeded(user); 9 } 10 11 return task.Result; 12 }); 13 } 14 15 public override Task<SignInResult> TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient) 16 { 17 ApplicationUser au = this.GetTwoFactorAuthenticationUserAsync().Result; 18 return base.TwoFactorAuthenticatorSignInAsync(code, isPersistent, rememberClient) 19 .ContinueWith<SignInResult>(task => 20 { 21 if (task.Result == SignInResult.Success && au != null) 22 { 23 LoginSucceeded(au); 24 } 25 26 return task.Result; 27 }); 28 } 29 30 public override Task SignOutAsync() 31 { 32 return base.SignOutAsync() 33 .ContinueWith(task => 34 { 35 LogoutSucceeded(Context.Request.Cookies["sessionId"]); 36 }); ; 37 } 38 39 public override bool IsSignedIn(ClaimsPrincipal principal) 40 { 41 if (!Context.User.Identity.IsAuthenticated) 42 { 43 if (Context.Request.Cookies.ContainsKey("sessionId")) 44 { 45 string userInfor = Context.Session.GetString(Context.Request.Cookies["sessionId"]); 46 if (!string.IsNullOrEmpty(userInfor)) 47 { 48 ApplicationUser user = JsonConvert.DeserializeObject<ApplicationUser>(userInfor); 49 if (user != null) 50 { 51 principal = Context.User = this.ClaimsFactory.CreateAsync(user).Result; 52 } 53 } 54 } 55 } 56 57 var flag = base.IsSignedIn(principal); 58 59 return flag; 60 } 61 62 private void LoginSucceeded(ApplicationUser user) 63 { 64 try 65 { 66 string sessionId = Guid.NewGuid().ToString(); 67 string userInfor = JsonConvert.SerializeObject(user); 68 Context.Session.SetString(sessionId, userInfor); 69 Context.Response.Cookies.Delete("sessionId"); 70 Context.Response.Cookies.Append("sessionId", sessionId); 71 } 72 catch (Exception xcp) 73 { 74 MessageQueue.Enqueue(MessageFactory.CreateMessage(xcp)); 75 } 76 } 77 78 private void LogoutSucceeded(string sessionId) 79 { 80 try 81 { 82 if (!string.IsNullOrEmpty(sessionId)) 83 { 84 Context.Session.Remove(sessionId); 85 } 86 } 87 catch (Exception xcp) 88 { 89 MessageQueue.Enqueue(MessageFactory.CreateMessage(xcp)); 90 } 91 }
重寫以後,須要在Startup.cs代碼ConfigureServices方法中註冊使用。負載均衡
services.AddIdentity<ApplicationUser, IdentityRole>(o => { o.Password.RequireNonAlphanumeric = false; }) .AddEntityFrameworkStores<MyDbContext>() .AddSignInManager<MySignInManager>() .AddDefaultTokenProviders();
2、Dockerwebapp
在實現自定義Identiy中的SignInManager類之後,將網站打包爲Docker鏡像(Image),而後根據須要運行多個容器(Container),這些容器的功能是相同的,實際上是多個網站實例,跑在不一樣的端口上面,至關於實現了分佈式部署。好比,運行三個容器的命令以下,分別跑在5000,5001和5002端口。分佈式
docker run --name webappdstr_0 -d -p 5000:80 -v /etc/localtime:/etc/localtime webapp:1.0 docker run --name webappdstr_1 -d -p 5001:80 -v /etc/localtime:/etc/localtime webapp:1.0 docker run --name webappdstr_2 -d -p 5002:80 -v /etc/localtime:/etc/localtime webapp:1.0
3、Nginx
在完成應用部署後,經過修改Nginx配置,實現負載均衡功能。主要配置以下:
1 # WebAppDistributed 2 server{ 3 listen 5443 ssl; 4 server_name www.webapp.com; 5 6 ssl_certificate ../cert/ssl.crt; 7 ssl_certificate_key ../cert/ssl.key; 8 9 location / { 10 proxy_pass http://webappserverd/; 11 proxy_redirect off; 12 proxy_set_header Host $host; 13 proxy_set_header X-Real-IP $remote_addr; 14 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 } 16 } 17 18 upstream webappserverd{ 19 server localhost:5000; 20 server localhost:5001; 21 server localhost:5002; 22 }
以上配置5000,5001和5002三個應用,即對應於Docker部署的三個網站應用。
4、Redis
Redis主要是實現Session的共享,經過Microsoft.Extensions.Caching.Redis.Core組件(經過Nuget獲取),在Startup.cs代碼ConfigureServices方法中添加Redis中間件服務。
// Redis services.AddDistributedRedisCache(option => { //redis 數據庫鏈接字符串 option.Configuration = Configuration.GetConnectionString("RedisConnection"); //redis 實例名 option.InstanceName = "master"; });
Redis的地址獲取的是appsettings.json配置中的配置項。
{ "ConnectionStrings": { ..., "RedisConnection": "192.168.1.16:6379" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } } }
5、重點難點
主要是總結下碰到的各類坑以及解決方案。
一、Session共享的須要DataProtection以及相關的配置支持
ASP.NET CORE對Session進行了加密,爲了可以在多個分佈式應用中實現共享,則須要使用相同的加密Key。實現共享的方式有多種,這裏採用自定義XmlRepository來實現。
1 public class CustomXmlRepository : IXmlRepository 2 { 3 private readonly string keyContent = @""; //使用前,插入key內容 4 5 public virtual IReadOnlyCollection<XElement> GetAllElements() 6 { 7 return GetAllElementsCore().ToList().AsReadOnly(); 8 } 9 10 private IEnumerable<XElement> GetAllElementsCore() 11 { 12 yield return XElement.Parse(keyContent); 13 } 14 public virtual void StoreElement(XElement element, string friendlyName) 15 { 16 if (element == null) 17 { 18 throw new ArgumentNullException(nameof(element)); 19 } 20 StoreElementCore(element, friendlyName); 21 } 22 23 private void StoreElementCore(XElement element, string filename) 24 { 25 } 26 }
以後,在Startup.cs中啓用DataProtection中間件,並進行配置。
1 // Set data protection. 2 services.AddDataProtection(configure => 3 { 4 configure.ApplicationDiscriminator = "WebApplication"; 5 }) 6 .SetApplicationName("WebApplication") 7 .AddKeyManagementOptions(options => 8 { 9 //配置自定義XmlRepository 10 options.XmlRepository = new CustomXmlRepository(); 11 }) 12 .ProtectKeysWithCertificate(new System.Security.Cryptography.X509Certificates.X509Certificate2("webapp.crt"));