文章 Forms 认证

如果你已经有一个基于 ASP.NET 或 ASP.NET Core 的登录系统,并且你希望使用这个系统登录 Limyee CMS,那么你应该使用 Forms 认证。

启用 Forms SSO 后,会发生以下情况:

  • 平台的登录和注册 URL 被重定向到指向外部身份验证系统
  • 当外部登录系统登录用户时,将设置一个基于 ASP.NET 或 ASP.NET Core 的用户验证 cookie,允许Limyee CMS 登录此用户,如果不存在帐户时将创建其新帐户。

Forms SSO 的工作原理

Forms SSO 通过 HTTP Cookie 将有关登录用户的安全消息从您的外部系统传递到 Limyee CMS。此消息是使用 ASP.NET 或 ASP.NET Core 提供的用户登录方式生成的。Limyee CMS 会自动验证消息并生成上下文用户。

当用户登录到您的主单点登录系统时,它将使用 ASP.NET 或 ASP.NET Core 的登录方式,并创建一个 Cookie,其中包含有关用户会话的数据。 当用户访问您的 Limyee CMS 时,Limyee CMS 将读取此 Cookie 会话数据。 如果存在给定用户名的用户,则登录该用户。 如果该用户不存在,则使用指定的会话数据创建一个新用户,然后登录该用户。

Forms SSO 的限制

要使 Forms SSO 正常工作,Cookie 需要在外部身份验证系统和平台之间共享。 由于浏览器允许共享 cookie 的方式,这意味着您的平台和登录页面必须使用相同的域名。 例如,login.example.com 和 mobile.example.com 使用相同的域:.example.com,因此可以与Forms SSO一起使用。请转到“系统管理>身份验证>域名”进行设置。

要使不同的系统能交互登录信息,必须使用相同的密钥保护数据,并将密钥设置相同的应用名。还应使用相同的授权方案类型,设置相同的 cookie 名称。

ASP.NET

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    },

    // Settings to configure shared cookie with ASP.NET Core app
    CookieName = ".Limyee",
    CookieDomain = ".example.com",
    CookieHttpOnly = true,
    AuthenticationType = "Authentication",
    TicketDataFormat = new AspNetTicketDataFormat(
       new DataProtectorShim(
           DataProtectionProvider.Create(new DirectoryInfo("PATH TO COMMON KEY RING FOLDER"),
           // Application name must be corresponding with Limyee CMS
           builder => builder.SetApplicationName("LimyeeApp"))
           .CreateProtector(
               "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
               // Must match the Scheme name used in the ASP.NET Core app, i.e. IdentityConstants.ApplicationScheme
               "Authentication",
               "v2"))),
    CookieManager = new ChunkingCookieManager()
});

ASP.NET Core

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("PATH TO COMMON KEY RING FOLDER"))
    // Application name must be corresponding with Limyee CMS
    .SetApplicationName("LimyeeApp");

//set default authentication schemes
var authenticationBuilder = services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = "Authentication";
    options.DefaultScheme = "Authentication";
    options.DefaultSignInScheme = "Authentication";
});

//add main cookie authentication
authenticationBuilder.AddCookie("Authentication", options =>
{
    options.Cookie.Name = ".Limyee";
    options.Cookie.HttpOnly = true;
    options.Cookie.Domain = ".example.com";
});

声名类型设置

名称

值

类型

描述

必填

电子邮件

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress

email

用于获取用户的电子邮件地址的类型。用户的电子邮件地址必须是唯一的。

否

个人资料

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/profile

string

用于获取用户的个人资料类型,并更新用户的个人资料。

否

角色

http://schemas.microsoft.com/ws/2008/06/identity/claims/role

string

用于获取用户的角色类型,用于更新用户的角色。

否

个人资料字段

序列化为多值的类型

CommonName=EXAMPL&Timezone=China+Standard+Time%3b480%3b(UTC%2b08%3a00)+Beijing%2c+Chongqing%2c+Hong+Kong%2c+Urumqi%3bChina+Standard+Time%3bChina+Daylight+Time%3b%3b

包含用户的昵称:CommonName 和用户资料包括但不全包括以下:

  • BirthDate (DateTime)
  • Gender (NotSet|Male|Female)
  • EnableEmoticons (bool)
  • DateFormat (string)
  • Bio (string)
  • PublicEmail (string)
  • Language (string)
  • WebAddress (string)
  • WebLog (string)
  • Signature (string)
  • Location (string)
  • Occupation (string)
  • Timezone (TimeZoneInfo)

角色字段

以英文逗号(,)相隔的 CSV 格式列表,用户将自动添加到 Everyone 和 Registered Users 角色中,而无需任何指派。

roles=Administrators,Moderators

创建声明

登录系统需要登入用户时,创建声明数据,以便 Limyee CMS 读取。声明的颁发者(Issuer)参数为 Limyee CMS 的 tenant ID。如果你 Limyee CMS 未开启租赁模式,则 tenant ID 为空的 GUID。

ASP.NET

通常在文件 Models/IdentityModels.cs

public class ApplicationUser : IdentityUser
{
    public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager)
    {
        // Note the authenticationType must match the one defined in UseCookieAuthentication
        var userIdentity = manager.CreateIdentity(this, "Authentication");

        // Add custom user claims here
        var claimsIssuer = Guid.Empty.ToString();
        userIdentity.AddClaim(new Claim(ClaimTypes.Name, "example", ClaimValueTypes.String, claimsIssuer));
        userIdentity.AddClaim(new Claim(ClaimTypes.Email, "example@example.com", ClaimValueTypes.Email, claimsIssuer));
        userIdentity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/profile", "CommonName=EXAMPL&Timezone=China+Standard+Time%3b480%3b(UTC%2b08%3a00)+Beijing%2c+Chongqing%2c+Hong+Kong%2c+Urumqi%3bChina+Standard+Time%3bChina+Daylight+Time%3b%3b", ClaimValueTypes.String, claimsIssuer));
        userIdentity.AddClaim(new Claim(ClaimTypes.Role, "roles=Administrators", ClaimValueTypes.String, claimsIssuer));

        return userIdentity;
    }

    public Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager)
    {
        return Task.FromResult(GenerateUserIdentity(manager));
    }
}

ASP.NET Core

public async Task UserSignInAsync(HttpContext context)
{
    var claimsIssuer = Guid.Empty.ToString();
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, "example", ClaimValueTypes.String, claimsIssuer),
        new Claim(ClaimTypes.Email, "example@example.com", ClaimValueTypes.Email, claimsIssuer),
        new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/profile", "CommonName=EXAMPL&Timezone=China+Standard+Time%3b480%3b(UTC%2b08%3a00)+Beijing%2c+Chongqing%2c+Hong+Kong%2c+Urumqi%3bChina+Standard+Time%3bChina+Daylight+Time%3b%3b", ClaimValueTypes.String, claimsIssuer),
        new Claim(ClaimTypes.Role, "roles=Administrators", ClaimValueTypes.String, claimsIssuer)
    };

    //create principal for the current authentication scheme
    var userIdentity = new ClaimsIdentity(claims, AuthenticationDefaults.AuthenticationScheme);
    var userPrincipal = new ClaimsPrincipal(userIdentity);

    //set value indicating whether session is persisted and the time at which the authentication was issued
    var authenticationProperties = new AuthenticationProperties
    {
        IsPersistent = true,
        IssuedUtc = DateTime.UtcNow,
        ExpiresUtc = DateTime.UtcNow.AddDays(7)
    };

    //sign in
    await context.SignInAsync(AuthenticationDefaults.AuthenticationScheme, userPrincipal, authenticationProperties);
}

故障排除

Limyee CMS 是使用基于 ASP.NET 或 ASP.NET Core 的 HttpContext 进行登录,如果 Limyee CMS 无法处理 SSO cookie,HttpContext 的 User 将设置为未认证,用户无法登录到系统,此过程不会抛出错误。你需查看子域名、密钥、授权方案类型和声明等的设置是否一致。

如果您无法访问控制面板(例如,由于 Forms SSO 配置问题而无法再登录),则可以使用紧急访问页面返回您的平台。您必须在服务器上访问此页面。页面地址: {{Limyee Site Url}}/localaccess,它将允许您在启用 Forms SSO 插件之前使用凭据登录。

使用 Forms SSO 的安全和隐私注意事项

使用电子邮件地址作为用户名

您不应使用电子邮件地址作为用户名。用户名用作平台中用户的可见唯一标识符,因此,如果使用电子邮件地址代替用户名将导致用户的电子邮件地址公开。如果您的现有系统使用电子邮件地址作为用户名,那么您应该考虑使用其他内容作为用户名,例如整数,UUID标识符,或者让用户在首次登录平台时创建其用户名。

参考:Share authentication cookies among ASP.NET apps | Microsoft Learn