使用标识 2.0 数据库对 ASP.NET 核心 1.0 应用程序进行身份验证

本文关键字:应用程序 核心 身份验证 NET ASP 标识 数据库 | 更新日期: 2023-09-27 17:56:29

我正在尝试创建一个新的 ASP.NET Core 1.0 Web应用程序,我希望它使用我已经设置的身份验证表。 这些表最初是由使用 Microsoft.ASPNet.Identity.EntityFramework 2.2.0 的 ASP.NET 4.6 Web 应用程序创建的

看起来Microsoft.AspNetCore.Identity.EntityFrameworkCore发生了变化。因为新的Core 1.0应用程序在尝试登录时会引发此错误:

处理请求时数据库操作失败。

SqlException:列名"规范化用户名"无效。 无效的列名"并发戳"。 无效的列名称"锁定结束"。 列名称"规范化电子邮件"无效。 无效的列名称"规范化用户名"。

project.json 是开箱即用的,看起来像这样:

"dependencies": {
"Microsoft.NETCore.App": {
  "version": "1.0.0",
  "type": "platform"
},
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Razor.Tools": {
  "version": "1.0.0-preview2-final",
  "type": "build"
},
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer.Design": {
  "version": "1.0.0",
  "type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
  "version": "1.0.0-preview2-final",
  "type": "build"
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
  "version": "1.0.0-preview2-final",
  "type": "build"
},
"Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
  "version": "1.0.0-preview2-final",
  "type": "build"
}

},

另外,我没有更改我的ApplicationDbContext,但是我看到了一些关于在该类中进行更改以解决此问题的文章

使用标识 2.0 数据库对 ASP.NET 核心 1.0 应用程序进行身份验证

这个SQL迁移脚本让我克服了上述障碍:

Alter Table ASPNETROLES
ADD
 ConcurrencyStamp varchar(255) null,               
 NormalizedName varchar(255) null
 Drop Table AspNetUserTokens
 CREATE TABLE [AspNetUserTokens] (
    [UserId]        NVARCHAR (450) NOT NULL,
    [LoginProvider] NVARCHAR (450) NOT NULL,
    [Name]          NVARCHAR (450) NOT NULL,
    [Value]         NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_AspNetUserTokens]
 PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [Name] ASC)
)
Alter Table AspNetUsers
 Add
 ConcurrencyStamp varchar(255) null,
 LockoutEnd DateTime null,
 NormalizedEmail varchar(255) null,
 NormalizedUserName varchar(255) null
Drop Table [AspNetRoleClaims]
CREATE TABLE [AspNetRoleClaims] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [ClaimType]  NVARCHAR (MAX) NULL,
    [ClaimValue] NVARCHAR (MAX) NULL,
    [RoleId]     NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_AspNetRoleClaims]
 PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId]
 FOREIGN KEY ([RoleId])
  REFERENCES [dbo].[AspNetRoles] ([Id]) ON DELETE CASCADE
)

GO
CREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId]
    ON [AspNetRoleClaims]([RoleId] ASC)
Alter Table AspNetUserLogins
   Add  ProviderDisplayName varchar(255) null

Microsoft没有关于如何迁移数据库的大量指导,但这为我解决了上述问题。

有一个包可用于执行此操作。从字面上看,它正是为了这个目的。它是Microsoft代码库的一部分,最近似乎针对Core 2.1进行了更新。

用于在 Microsoft.AspNet.Identity.EntityFramework 和 Microsoft.AspNetCore.Identity.EntityFrameworkCore 之间共享身份数据库的兼容层。

https://www.nuget.org/packages/Microsoft.AspNet.Identity.AspNetCoreCompat/

https://github.com/aspnet/Identity/tree/master/src/AspNetCoreCompat

它使用如下方法处理两个模式之间的"差异":

    /// <summary>
    ///     Normalized email
    /// </summary>
    public string NormalizedEmail {
        get
        {
            return Email.ToUpperInvariant();
        }
        set { }
    }
    /// <summary>
    ///     Concurrency stamp
    /// </summary>
    public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();

我找不到任何真正的文档,但是我已经完成了以下操作,并且似乎工作正常:

  • 在旧网站 (.NET 4.6) 中安装兼容包
  • 您必须将所有对IdentityRoleIdentityUserIdentityDbContext等的引用更改为兼容包中的类。

    using Compat = Microsoft.AspNet.Identity.CoreCompat;
    // update to use the compatibility class
    public class ApplicationDbContext : Compat.IdentityDbContext<ApplicationUser>
    // change all instances, such as this
    Compat.IdentityUser user = await _repo.FindUser(context.UserName, context.Password);  
    
  • 您必须将数据库升级到新格式(基本上是添加几列并更改一些数据类型)。这是最棘手的阶段!您肯定希望在暂存环境中执行此操作。我正在使用 Azure,所以我只是克隆了数据库。

  • 在GitHub上,我发现了一些名为Migration.zip的@samnpathdr迁移脚本。有几个脚本要逐个运行。我建议一次运行一个命令,以确保它全部运行。
  • 目前,他的脚本中有一个表是他的实现(AspNetUserRolePermissions)自定义的,因此请删除对该表的引用。
  • 如果有任何其他表引用AspNetUsers表,则必须删除约束,更新数据类型并重新添加约束。例如,我有一个链接到AspNetUserNotes表,所以我需要运行ALTER TABLE UserProfileNote ALTER COLUMN AspNetUsersId nvarchar(450) NOT NULL;(删除约束后)。首先编写约束脚本!
  • 如果您为 Core 启用了任何"自动迁移",请小心,因为就个人而言,在这种更改之后,我不会信任它们。应重置为基线,或者根本不执行 EF 迁移。

https://github.com/aspnet/Docs/issues/6425

我手动编写了从旧Identity到新的迁移,该应用程序适用于新老用户。如果您想节省一些手动工作,这里是迁移:

public partial class Identity : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<string>(
            name: "NormalizedName",
            table: "AspNetRoles",
            type: "nvarchar(256)",
            maxLength: 256,
            nullable: true);
        migrationBuilder.AddColumn<string>(
            name: "ConcurrencyStamp",
            table: "AspNetRoles",
            type: "nvarchar(max)",
            nullable: true);
        migrationBuilder.RenameColumn(
            name: "LockoutEndDateUtc",
            table: "AspNetUsers",
            newName: "LockoutEnd");
        migrationBuilder.AddColumn<string>(
            name: "ConcurrencyStamp",
            table: "AspNetUsers",
            type: "nvarchar(max)",
            nullable: true);
        migrationBuilder.AddColumn<string>(
            name: "NormalizedEmail",
            table: "AspNetUsers",
            type: "nvarchar(256)",
            maxLength: 256,
            nullable: true);
        migrationBuilder.AddColumn<string>(
            name: "NormalizedUsername",
            table: "AspNetUsers",
            type: "nvarchar(256)",
            maxLength: 256,
            nullable: true);
        migrationBuilder.AddColumn<string>(
            name: "ProviderDisplayName",
            table: "AspNetUserLogins",
            type: "nvarchar(max)",
            nullable: true);
        migrationBuilder.CreateTable(
            name: "AspNetRoleClaims",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                RoleId = table.Column<string>(type: "nvarchar(128)", nullable: false),
                ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
                ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
                table.ForeignKey(
                    name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
                    column: x => x.RoleId,
                    principalTable: "AspNetRoles",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
        migrationBuilder.CreateTable(
            name: "AspNetUserTokens",
            columns: table => new
            {
                UserId = table.Column<string>(type: "nvarchar(128)", nullable: false),
                LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
                Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
                Value = table.Column<string>(type: "nvarchar(max)", nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
                table.ForeignKey(
                    name: "FK_AspNetUserTokens_AspNetUsers_UserId",
                    column: x => x.UserId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "ConcurrencyStamp",
            table: "AspNetRoles");
        migrationBuilder.DropColumn(
            name: "NormalizedName",
            table: "AspNetRoles");
        migrationBuilder.RenameColumn(
            name: "LockoutEnd",
            table: "AspNetUsers",
            newName: "LockoutEndDateUtc");
        migrationBuilder.DropColumn(
            name: "ConcurrencyStamp",
            table: "AspNetUsers");
        migrationBuilder.DropColumn(
            name: "NormalizedEmail",
            table: "AspNetUsers");
        migrationBuilder.DropColumn(
            name: "NormalizedUsername",
            table: "AspNetUsers");
        migrationBuilder.DropColumn(
            name: "ProviderDisplayName",
            table: "AspNetUserLogins");
        migrationBuilder.DropTable("AspNetRoleClaims");
        migrationBuilder.DropTable("AspNetUserTokens");
    }
}

还要记住,这是单向迁移。为了能够仍然使用旧的解决方案,您可以强制.net核心使用旧的密码哈希算法

services.Configure<PasswordHasherOptions>(options => options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2);