Popular blog tags

前言

在同一个一级域名下有很二级域名系统,如:https://www.iaspnetcore.com/, https://search.iaspnetcore.com 等.

但是这些系统都是www.iaspnetcore.com的二级域。

要实现上述功能,首先要了解一个网站有哪些认证登陆方式。Asp .Net Core的认证有很多方式可以实现,如:cookie认证,jwt认证等等。我把它精简一下,从浏览器和服务器之间的认证信息载体的传递的角度,其实只有两种方式可以选择:

1.使用cookie方式实现登陆认证:

浏览器和服务器之间的认证信息传递,用cookie。系统自带的认证方式,最常用的一种。

2.使用token方式实现登陆认证:

如jwt,IdentityServer等,都是基于token在浏览器和服务器如何认证、传递的实现方案。

本次采用cookie方式实现登陆认证。这样的情况就可以在不引用其它框架的情况下,直接基于Cookie实现同域单点登录SSO(Single Sign-on)

要实现的功能:

实现一个系统登录,其它子系统都登录。一个子系统退出。其它子系统也都退出的功能.

 

要实现上述目的,需要做到以下几点:

1.所有的站点都要使用同一个cookie

2.所有的站点都要使用同一种对cookie的加密、解密方式

3.所有的站点都要使用同一种程序名AplicationName

 

 

cookie登陆过程

在一个Web应用程序中,通常使用一个cookie来表示一个已经登录的用户。

一般的流程是:

1.用户单击登录,进入登录页面。

2.输入有效凭证后(用户名和用户密码),服务器发送给用户浏览器的响应头包含一个带 Set-Cookie 头,其内容为加密信息。

3.被设置上domain 例如 giant.com,每次浏览器向这个domain发送请求时,设置在这个domain上的cookie也会被带上。

4.在服务器上,cookie将被解密,然后使用解密后的内容来创建用户的 Identity。

 

实现思路:

1.发送同一个cookie:浏览器浏览不同的站点时,要让浏览器向web服务站点发送同一个cookie,这个只要把  CookieDomain设置为根域名就可达到让浏览器访问不同子域名时发送同一个cookie。如:CookieDomain=".iaspnetcore.com",

2.所有站点都使用同一种数据解密方式:这个需要把数据保护配置到同一个地方,同一主域名下所有的网站都都使用同一中数据解密方式。如:

services.AddDataProtection()
                    .SetApplicationName("isapnetcore")
                    .PersistKeysToFileSystem(new DirectoryInfo(keysFolder));

 

实现方法

  

1.修改配置主要统一数据加密方式与统一应用名称
这样其它子域的Cookie加密数据就能识别。

2.配置统一的Cookie名称与写的域名为根域。
这样所有子域都能发现与识别此登录的Cookie信息
3.这样就可以实现一个系统登录,其它子系统都登录
一个子系统退出。其它子系统也都退出的功能

 

添加需要的引用:

path:\Startup.cs

using System.Runtime.InteropServices;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Authentication.Cookies;

 

Startup代码:

添加引用 using Microsoft.AspNetCore.DataProtection,services.AddDataProtection()要用到。添加如下代码:

 public void ConfigureServices(IServiceCollection services)
        {

        

        
            #region 配置数据保护,Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks in ASP.NET Core
            //https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1

            var keysFolder = "";

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                keysFolder = "C:\\artifacts";
            else
                keysFolder = "/var/share/ ";

            services.AddDataProtection()
                    .SetApplicationName("isapnetcore")
                    .PersistKeysToFileSystem(new DirectoryInfo(keysFolder));

            #endregion
}
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IXmlRepository, CustomFileXmlRepository>();
            services.AddDataProtection(configure =>
            {
                configure.ApplicationDiscriminator = "Htw.Web";
            });
            // Add framework services.
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
                LoginPath = new PathString("/Account/Unauthorized/"),
                AccessDeniedPath = new PathString("/Account/Forbidden/"),
                AutomaticAuthenticate = true,
                AutomaticChallenge = false,
                CookieHttpOnly = true,
                CookieName = "MyCookie",
                ExpireTimeSpan = TimeSpan.FromHours(2),
#if !DEBUG
                CookieDomain="h.cn",
#endif
                DataProtectionProvider = null
            });
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
 

登录代码:

 
        public async void Login()
        {
            if (!HttpContext.User.Identities.Any(identity => identity.IsAuthenticated))
            {
                var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme));
                await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user);

                HttpContext.Response.ContentType = "text/plain";
                await HttpContext.Response.WriteAsync("Hello First timer");
            }
            else
            {
                HttpContext.Response.ContentType = "text/plain";
                await HttpContext.Response.WriteAsync("Hello old timer");
            }
        }
 
直接上代码,我们需要先封装一个XmlRepository,Key的格式如下:
<?xml version="1.0" encoding="utf-8"?>
<key id="cbb8a41a-9ca4-4a79-a1de-d39c4e307d75" version="1">
  <creationDate>2016-07-23T10:09:49.1888876Z</creationDate>
  <activationDate>2016-07-23T10:09:49.1388521Z</activationDate>
  <expirationDate>2116-10-21T10:09:49.1388521Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>WYgZNh/3dOKRYJ1OAhVqs56pWPMHei15Uj44DPLWbYUiCpNVEBwqDfYAUq/4jBKYrNoUbaRkGY5o/NZ6a2NTwA==</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>
 
XmlRepository代码:
public class CustomFileXmlRepository : IXmlRepository
    {
        private readonly string filePath = @"C:\keys\key.xml";

        public virtual IReadOnlyCollection<XElement> GetAllElements()
        {
            return GetAllElementsCore().ToList().AsReadOnly();
        }

        private IEnumerable<XElement> GetAllElementsCore()
        {
            yield return XElement.Load(filePath);
        }
        public virtual void StoreElement(XElement element, string friendlyName)
        {
            if (element == null)
            {
                throw new ArgumentNullException(nameof(element));
            }
            StoreElementCore(element, friendlyName);
        }

        private void StoreElementCore(XElement element, string filename)
        {
        }
    }
 

注意 

C:\keys\key.xml 这个文件路径可以更改,还有就是也可用共享目录或数据库来实现统一管理

到此可以登录试一下。

 
建议阅读: