零、背景介绍

在学习ASP.NET CORE开发的过程中,身份认证是必须考虑的一项必要的组件。ASP.NET CORE Identity是由微软官方开发的一整套身份认证组件,兼具完整性和自由度。Docker作为目前虚拟化的主流解决方案,可以很快捷地实现应用的打包和部署。Nginx作为反向代理,结合Docker多环境部署,可以实现负载均衡功能。而在分布式环境下,Session的共享,特别是登录状态的共享是难以逾越的一个“小”问题。

然而,这个“小”问题,却让我花费了大量的时间搞清楚了相互之间的协作关系,并成功实现了Docker+Nginx+Redis多种组件相结合的解决方案。

环境:ASP.NET Core 2.0

一、ASP.NET CORE Identity

为了实现Session共享,需要在Cookie中存储Session的ID信息以及用户信息,从而实现在多个应用之间的信息共享。有关ASP.NET CORE Identity的介绍,这里不在赘述。

ASP.NET CORE Identity主要包括UserManager和SignInManager两个主要的管理类,从名称可以看出来SignInManager实现的是登陆的管理,因为涉及到登录状态以及登录用户信息的共享,所以我们需要实现自定义的SignInManager类,重写其中最为重要的登录和登出方法。

  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方法中注册使用。

  1. services.AddIdentity<ApplicationUser, IdentityRole>(o =>
  2. {
  3. o.Password.RequireNonAlphanumeric = false;
  4. })
  5. .AddEntityFrameworkStores<MyDbContext>()
  6. .AddSignInManager<MySignInManager>()
  7. .AddDefaultTokenProviders();

二、Docker

    在实现自定义Identiy中的SignInManager类以后,将网站打包为Docker镜像(Image),然后根据需要运行多个容器(Container),这些容器的功能是相同的,其实是多个网站实例,跑在不同的端口上面,相当于实现了分布式部署。比如,运行三个容器的命令如下,分别跑在5000,5001和5002端口。

  1. docker run --name webappdstr_0 -d -p : -v /etc/localtime:/etc/localtime webapp:1.0
  2. docker run --name webappdstr_1 -d -p : -v /etc/localtime:/etc/localtime webapp:1.0
  3. docker run --name webappdstr_2 -d -p : -v /etc/localtime:/etc/localtime webapp:1.0

三、Nginx

在完成应用部署后,通过修改Nginx配置,实现负载均衡功能。主要配置如下:

  1. # WebAppDistributed
  2. server{
  3. listen 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:;
  20. server localhost:;
  21. server localhost:;
  22. }

Nginx config

     以上配置5000,5001和5002三个应用,即对应于Docker部署的三个网站应用。

四、Redis

    Redis主要是实现Session的共享,通过Microsoft.Extensions.Caching.Redis.Core组件(通过Nuget获取),在Startup.cs代码ConfigureServices方法中添加Redis中间件服务。

  1. // Redis
  2. services.AddDistributedRedisCache(option =>
  3. {
  4. //redis 数据库连接字符串
  5. option.Configuration = Configuration.GetConnectionString("RedisConnection");
  6. //redis 实例名
  7. option.InstanceName = "master";
  8. });

Redis的地址获取的是appsettings.json配置中的配置项。

  1. {
  2. "ConnectionStrings": {
  3. ...,
  4. "RedisConnection": "192.168.1.16:6379"
  5. },
  6. "Logging": {
  7. "IncludeScopes": false,
  8. "LogLevel": {
  9. "Default": "Warning"
  10. }
  11. }
  12. }

五、重点难点

主要是总结下碰到的各种坑以及解决方案。

1、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

ASP.NET Identity实现分布式Session,Docker+Nginx+Redis+ASP.NET CORE Identity的更多相关文章

  1. ASP.NET WebApi 基于分布式Session方式实现Token签名认证

    一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...

  2. ASP.NET WebApi 基于分布式Session方式实现Token签名认证(发布版)

    一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...

  3. Centos8 Docker+Nginx部署Asp.Net Core Nginx正向代理与反向代理 负载均衡实现无状态更新

    首先了解Nginx 相关介绍(正向代理和反向代理区别) 所谓代理就是一个代表.一个渠道: 此时就涉及到两个角色,一个是被代理角色,一个是目标角色,被代理角色通过这个代理访问目标角色完成一些任务的过程称 ...

  4. asp.netcore 自动挡Docker Nginx Redis(滴滴滴,自动挡)

    前言 上一章介绍了Docker通过多条命令创建启动运行Docker容器,由此可见这样一个个去创建单独的容器也是相当麻烦的,比如要在某个复杂项目中用DB.缓存.消息等等,这样我们还要去一个个再创建,为此 ...

  5. Spring Boot 分布式Session状态保存Redis

    在使用spring boot做负载均衡的时候,多个app之间的session要保持一致,这样负载到不同的app时候,在一个app登录之后,而打到另外一台服务器的时候,session丢失. 常规的解决方 ...

  6. (38)Spring Boot分布式Session状态保存Redis【从零开始学Spring Boot】

    [本文章是否对你有用以及是否有好的建议,请留言] 在使用spring boot做负载均衡的时候,多个app之间的session要保持一致,这样负载到不同的app时候,在一个app登录之后,而访问到另外 ...

  7. docker+nginx+redis部署前后端分离项目!!!

    介绍本文用的经典的前后端分离开源项目.项目的拉取这些在另一篇博客!!! 其中所需要的前后端打包本篇就不做操作了!!不明白的去看另一篇博客!!! 地址:http://www.cnblogs.com/ps ...

  8. ASP.NET Core中间件实现分布式 Session

    1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件的配置 1.2. 依赖注入中间件 1.3. Cookies ...

  9. ASP.NET Core中间件实现分布式 Session(转载)

    ASP.NET Core中间件实现分布式 Session 1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件 ...

随机推荐

  1. Day9 - J - 吉哥系列故事——恨7不成妻 HDU - 4507

    单身! 依然单身! 吉哥依然单身! DS级码农吉哥依然单身! 所以,他生平最恨情人节,不管是214还是77,他都讨厌! 吉哥观察了214和77这两个数,发现: 2+1+4=7 7+7=7*2 77=7 ...

  2. Day3-L-Cup HDU2289

    The WHU ACM Team has a big cup, with which every member drinks water. Now, we know the volume of the ...

  3. JavaScript之this的用法

    本文我们介绍下js中this的用法. 由上图可得,默认this指向window,而在node.js中this默认指向global. 由上图可得: 1.原型链为o->MyClass.prototy ...

  4. IDEA spring mvc整合mybatis

    准备工作 IDEA 2019.3.1 MySql 8.0.17 Tomcat 7.0.9 开始步骤 一.创建一个项目,添加Web支持 点击菜单:File->NEW->Project 选择左 ...

  5. 前端学习笔记系列一:3 Vue中的nextTick

    一.示例 先来一个示例了解下关于Vue中的DOM更新以及nextTick的作用. 模板 <div class="app"> <div ref="msgD ...

  6. MongoDB分片技术原理和高可用集群配置方案

    一.Sharding分片技术 1.分片概述 当数据量比较大的时候,我们需要把数分片运行在不同的机器中,以降低CPU.内存和Io的压力,Sharding就是数据库分片技术. MongoDB分片技术类似M ...

  7. 配置antMatchers(HttpMethod.GET,"/**").permitAll()当时仍然会校验

    .antMatchers(HttpMethod.GET,"/**").permitAll() .anyRequest().authenticated() .and() .addFi ...

  8. NO34 awk

  9. arm linux 移植 PHP

    背景: PHP 是世界上最好的语言. host平台 :Ubuntu 16.04 arm平台 : 3531d arm-gcc :4.9.4 php :7.1.30 zlib :1.2.11 libxml ...

  10. SPFA和堆优化的Dijk

    朴素dijkstra时间复杂度$O(n^{2})$,通过使用堆来优化松弛过程可以使时间复杂度降到O((m+n)logn):dijkstra不能用于有负权边的情况,此时应使用SPFA,两者写法相似. 朴 ...