[Blazor 5,6] 소셜로긴 IdentityEntity Mysql 5.7 호환시키기~

최근 블레이즈 입문하고 있으며, Net5이상 Blazor 서버에서 Mysql DB를 사용한 페이스북 연동 삽질과
정 정리하였습니다.

Blazor 닷넷코어3기준, 페이스북 연동은 아래 아티컬을 참고하면 되겠습니다. ( MSSQL Base)

시작참고링크 : How To Implement Facebook Authentication And Authorization In Server-Side Blazor App | Hacker Noon

마이그레이션 목표 버전

<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="5.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.11" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.11">

첫번째 문제, Identity Entity Model이 버전이 올라감에 따라 스키마 변경이 있으며
마이그레이션툴인 dotnet ef 로 , mysql 스키마를 자동만들어 내기가 어렵습니다. ( ef db 자동 마이그레이션 논외로 하고, docker를통해 db를 띄우고 관련 스키마를 자동생성하는 방식으로 이어가겠습니다.)

MsSql 스키마를 참고하여, 변환해도 되지만 여러번 삽질끝에
닷넷코어 Identity 5.0 소셜인증(FB)및 기본인증을 위해서 아래 스크립트를 이용해 Mysql 5.7 호환용 테이블을생성할수 있습니다. ( mysql 5.7기준, 최신으로 유지하는 레파지토리가 있으면 공유부탁드립니다. 찾지를 못하여 몇번의 삽질을~)

/*
 * Action: 인증 DB생성
 * Purpose: firstsql.mysql , 도커 up시 스키마를 자동생성
 * 참고 :
*/
CREATE DATABASE `auth` default character set utf8 collate utf8_general_ci;
USE `auth`;
SET NAMES 'utf8';

	
CREATE TABLE `AspNetRoles` (
  `Id` varchar(128) NOT NULL,
  `Name` varchar(256) NOT NULL,
  PRIMARY KEY (`Id`)
);

CREATE TABLE `AspNetUsers` (
  `Id` varchar(128) NOT NULL,
  `Email` varchar(256) DEFAULT NULL,
  `NormalizedEmail` varchar(256) DEFAULT NULL,  
  `EmailConfirmed` tinyint(1) NOT NULL,
  `PasswordHash` longtext,
  `SecurityStamp` longtext,
  `PhoneNumber` longtext,
  `PhoneNumberConfirmed` tinyint(1) NOT NULL,
  `TwoFactorEnabled` tinyint(1) NOT NULL,
  `LockoutEndDateUtc` datetime DEFAULT NULL,
  `LockoutEnabled` tinyint(1) NOT NULL,
  `LockoutEnd` datetime DEFAULT NULL,
  `AccessFailedCount` int(11) NOT NULL,
  `UserName` varchar(256) NOT NULL,
  `NormalizedUserName` varchar(256) NOT NULL,
  `ConcurrencyStamp` longtext NOT NULL,   
  PRIMARY KEY (`Id`)
);

CREATE TABLE `AspNetUserClaims` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `UserId` varchar(128) NOT NULL,
  `ClaimType` longtext,
  `ClaimValue` longtext,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `Id` (`Id`),
  KEY `UserId` (`UserId`),
  CONSTRAINT `ApplicationUser_Claims` FOREIGN KEY (`UserId`) REFERENCES `AspNetUsers` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
);

CREATE TABLE `AspNetUserLogins` (
  `LoginProvider` varchar(128) NOT NULL,
  `ProviderDisplayName` varchar(128) NOT NULL,
  `ProviderKey` varchar(128) NOT NULL,
  `UserId` varchar(128) NOT NULL,
  PRIMARY KEY (`LoginProvider`,`ProviderKey`,`UserId`),
  KEY `ApplicationUser_Logins` (`UserId`),
  CONSTRAINT `ApplicationUser_Logins` FOREIGN KEY (`UserId`) REFERENCES `AspNetUsers` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
);

CREATE TABLE `AspNetUserRoles` (
  `UserId` varchar(128) NOT NULL,
  `RoleId` varchar(128) NOT NULL,
  `ConcurrencyStamp` longtext NOT NULL,
  PRIMARY KEY (`UserId`,`RoleId`),
  KEY `IdentityRole_Users` (`RoleId`),
  CONSTRAINT `ApplicationUser_Roles` FOREIGN KEY (`UserId`) REFERENCES `AspNetUsers` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION,
  CONSTRAINT `IdentityRole_Users` FOREIGN KEY (`RoleId`) REFERENCES `AspNetRoles` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ;

로컬 테스트를 위한 도커스크립트는 다음과 같이 구성하여
docker-compose up -d 으로, identity에 대응하는 로컬 개발 DB 구동가능합니다.

version: ‘2’

services:
  authdb:
    image: mysql:5.7
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    ports:
      - 13306:3306
    volumes:
      - ./init/firstsql.mysql:/docker-entrypoint-initdb.d/init.sql      

    environment:
      MYSQL_ROOT_PASSWORD: "root"
      TZ: "Asia/Seoul"

두번째 문제는, MSSQL에서는 지원하는듯 보이며, IdentityUser에 timestamp(LockoutEnd) 가 추가되었는데 timestamp개념이 Mysql 5.7에 없어서 호환이 불가능하며 다음과 같은 코드로, Entity모델 맵핑변환을 해주어야 합니다.

    private ValueConverter GetDateTimeToDateTimeOffsetConverter()
    {
        var converter = new ValueConverter<DateTimeOffset, DateTime>(
                                    requestedWithOffset => requestedWithOffset.DateTime,
                                    requested => new DateTimeOffset(requested, TimeSpan.Zero)
                                    );

        return converter;
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<IdentityRole>(entity => entity.Property(m => m.Id).HasMaxLength(450));

        builder.Entity<IdentityUser>(entity =>
            entity.Property(e => e.LockoutEnd)
                .HasColumnName("LockoutEnd")
                .HasColumnType("timestamp")
                .HasConversion(GetDateTimeToDateTimeOffsetConverter())
        );
.............,

DB연결및, Identity DBContext 사용을 위한, 최종 완성 Full Code

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace CommunicationCenter.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        private readonly AppSettings _appSettings;

        private readonly string database = "auth";

        public ApplicationDbContext(AppSettings appSettings)            
        {
            _appSettings = appSettings;
        }

        private ValueConverter GetDateTimeToDateTimeOffsetConverter()
        {
            var converter = new ValueConverter<DateTimeOffset, DateTime>(
                                        requestedWithOffset => requestedWithOffset.DateTime,
                                        requested => new DateTimeOffset(requested, TimeSpan.Zero)
                                        );

            return converter;
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<IdentityRole>(entity => entity.Property(m => m.Id).HasMaxLength(450));

            builder.Entity<IdentityUser>(entity =>
                entity.Property(e => e.LockoutEnd)
                    .HasColumnName("LockoutEnd")
                    .HasColumnType("timestamp")
                    .HasConversion(GetDateTimeToDateTimeOffsetConverter())
            );

            builder.Entity<IdentityUserLogin<string>>(entity =>
            {
                entity.Property(m => m.LoginProvider).HasMaxLength(127);
                entity.Property(m => m.ProviderKey).HasMaxLength(127);
            });

            builder.Entity<IdentityUserRole<string>>(entity =>
            {
                entity.Property(m => m.UserId).HasMaxLength(127);
                entity.Property(m => m.RoleId).HasMaxLength(127);
            });

            builder.Entity<IdentityUserToken<string>>(entity =>
            {
                entity.Property(m => m.UserId).HasMaxLength(127);
                entity.Property(m => m.LoginProvider).HasMaxLength(127);
                entity.Property(m => m.Name).HasMaxLength(127);
            });
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                string dbOption = "Convert Zero Datetime=true;";
                string dbConnectionString = string.Empty;
                dbConnectionString = _appSettings.DBConnectionAuth + $"database={database};" + dbOption;
                optionsBuilder.UseMySQL(dbConnectionString);

                base.OnConfiguring(optionsBuilder);
            }
        }

    }
}

Blazor 시작템플릿에서, 소셜인증및 소셜가입하기 기능이 작동되면 미션 완료입니다.

image

위내용은, Azure를 활용하면 이와같은 수고는 필요없으며~ 타클라우드 DB연동및 온프레미스환경에서 도움될것같습니다. AWS클라우드 계열은 대부분 DB관리 차원에서 오로라(~mysql5.7)를 채택하고 점유율이 높은것으로 알고 있습니다.(조사하지는 않음)

이 내용이 닷넷이 Azure뿐만아니라 AWS진영(리눅스 Ec2에 닷넷코어가 가득) 저변확대에도 도움이 되었으면 좋겠습니다~

좋아요 6