ASP.NET Identity實現分佈式Session,Docker+Nginx+Redis+ASP.NET CORE Identity

 零、背景介紹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         }
MySignInManager

   重寫以後,須要在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    }
Nginx config

     以上配置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     }
CustomXmlRepository

    以後,在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"));
Startup.cs
相關文章
相關標籤/搜索