今回やること
アカウント認証を実装します。
初めに
既存のプロジェクトにASP.NET Identityを導入する方法はないようです。。。
ダミーで認証ありのプロジェクトを作って、そこから必要なものをコピーして編集して…みたいな方法しかなかったです。
さすがに面倒くさすぎるので、最初から作り直すことにしました。
認証機能付きプロジェクトを作る
差分を取り込む
NuGetでNpgsqlをインストールします。
上記で作成したプロジェクトともともとのプロジェクトの差分を取り込みます。(第2回参照)
appsettings.jsonのDefaultConnectionをPostgresに戻します。
{"ConnectionStrings":{"DefaultConnection":"Server=localhost;Port=5432;Database=SMSDB;User ID=postgres;Password=password;Enlist=true"},"Logging":{"LogLevel":{"Default":"Information","Microsoft":"Warning","Microsoft.Hosting.Lifetime":"Information"}},"AllowedHosts":"*"}Startup.csのConfigureServicesのUseSqlServerをUseNpgsqlに戻します。
// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext<ApplicationDbContext>(options=>//options.UseSqlServer(options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));services.AddDatabaseDeveloperPageExceptionFilter();services.AddDefaultIdentity<IdentityUser>(options=>options.SignIn.RequireConfirmedAccount=true).AddEntityFrameworkStores<ApplicationDbContext>();services.AddControllersWithViews();}自分で作ったファイルたちをコピーします。
コントローラ、モデル、ビュー等。(WinMergeで差分を見つけていきました)
次に自前のDbContextからApplicationDbContextに一部記述を移植して、自前のDbContextを削除、自前のDbContextを参照している個所をApplicationDbContextに書き換えます。
usingMicrosoft.AspNetCore.Identity.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore;usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceSalaryManagementSystem.Data{publicclassApplicationDbContext:IdentityDbContext{publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}publicDbSet<SalaryManagementSystem.Models.Salary>Salary{get;set;}// 移植箇所}}最後に、2つあるMigrationsフォルダを丸ごと削除します。
Data配下とプロジェクト直下にあります。
パッケージマネージャコンソールから、Add-MigrationとUpdate-DataBaseコマンドを実行します。
認証用のテーブルを作るためのマイグレーションが必要なのですが、上記コマンドを実行するためには一度既存のマイグレーションによる生成ファイルは削除する必要がありました。
そのため、Migrationsフォルダを削除しています。
また、DbContextは1つに統合できたので統合しました。
マイグレーションのコマンドで、モデルに対応するテーブル(Salaryテーブル)は既にあるので警告が出ます。
このテーブルだけでなく全部のテーブル作成がロールバックされてしまうので、Salaryテーブルは削除して、実行します。
実行できると、認証機能用のテーブルができます。
ひとまず起動してみる
右上にRegisterとLoginが表示されるようになりました。
例によって日本語化しておきます。ログイン周りの定義は_Layout.cshtmlから呼び出している_LoginPartical.cshtmlに定義されています。
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ulclass="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Manage/Index"title="Manage">ようこそ @User.Identity.Name!</a></li><liclass="nav-item"><formclass="form-inline"asp-area="Identity"asp-page="/Account/Logout"asp-route-returnUrl="@Url.Action("Index","Home",new{area = ""})"><buttontype="submit"class="nav-link btn btn-link text-dark">ログアウト</button></form></li>
}
else
{
<liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Register">アカウント登録</a></li><liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Login">ログイン</a></li>
}
</ul>デフォルトの動きを確認
まずアカウント登録の画面に遷移すると下記のような画面になります。
メールアドレスを入力するようになっていますが、面倒なのでユーザIDにしたいです。
あと右側の文言は「外部サービス連携は使えませんよ」的なメッセージなので消したいです。
適当に入力しRegisterを押します。
…先に言ってほしいやつですね。この辺を右側に書きましょうか。
ログイン失敗しました。どうやらメール認証しないとダメみたいですね。
メール認証…いったんオフにしたいですね…
今回はテーブルデータを更新して、認証したことにします。
AspNetUsersテーブルのEmailConfirmedをtrueにします。
もう一度ログイン。ログインできました。メールアドレス=ユーザIDはちょっと気に入らないですね。
変更したいところ
・メールアドレスとパスワードではなく、IDとパスワードにしたい。(あと表示名)
→つまり独自のユーザー情報モデルを作る
・登録画面の右のペインはパスワードのルールに書き換える
独自のユーザー情報モデルを作る
こちらを参考にします。
https://kiyokura.hateblo.jp/entry/2014/06/23/010749
ユーザ情報モデルはSMSUserというクラスにします。ASP.NET CoreだとIUserではなくIdentityUserというインターフェースになります。
usingMicrosoft.AspNetCore.Identity;usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel.DataAnnotations;usingSystem.ComponentModel.DataAnnotations.Schema;usingSystem.Linq;usingSystem.Threading.Tasks;namespaceSalaryManagementSystem.Models{[Table("SMSUser")]publicclassSMSUser:IdentityUser{[Key][Column("UserId")][Display(Name="ユーザーID")]publicstringSMSUserId{get;set;}[Column("UserName")][Display(Name="ユーザー名")]publicstringSMSUserName{get;set;}[Display(Name="パスワード")]publicstringPassword{get;set;}publicboolIsLocked{get;set;}publicintLoginFailCount{get;set;}publicDateTimeCreateDate{get;set;}publicDateTimeUpdateDate{get;set;}[NotMapped]publicoverridestringId{get;set;}[NotMapped]publicoverridestringNormalizedUserName{get;set;}[NotMapped]publicoverridestringEmail{get;set;}[NotMapped]publicoverridestringNormalizedEmail{get;set;}[NotMapped]publicoverrideboolEmailConfirmed{get;set;}[NotMapped]publicoverridestringPasswordHash{get;set;}[NotMapped]publicoverridestringSecurityStamp{get;set;}[NotMapped]publicoverridestringConcurrencyStamp{get;set;}[NotMapped]publicoverridestringPhoneNumber{get;set;}[NotMapped]publicoverrideboolPhoneNumberConfirmed{get;set;}[NotMapped]publicoverrideboolTwoFactorEnabled{get;set;}[NotMapped]publicoverrideDateTimeOffset?LockoutEnd{get;set;}[NotMapped]publicoverrideboolLockoutEnabled{get;set;}[NotMapped]publicoverrideintAccessFailedCount{get;set;}}}ユーザストアクラスを作ります。雰囲気で実装しました。よくわかっていません…。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Identity;usingMicrosoft.EntityFrameworkCore;usingSalaryManagementSystem.Data;namespaceSalaryManagementSystem.Models{publicclassSMSUserStore:IUserPasswordStore<SMSUser>{privateApplicationDbContext_context;privatestaticList<SMSUser>Users=newList<SMSUser>();publicSMSUserStore(ApplicationDbContextcontext){_context=context;}publicTask<IdentityResult>CreateAsync(SMSUseruser,CancellationTokencancellationToken){_context.SMSUsers.Add(user);_context.SaveChangesAsync();return(Task<IdentityResult>)Task.Delay(0);}publicTask<IdentityResult>DeleteAsync(SMSUseruser,CancellationTokencancellationToken){_context.SMSUsers.Remove(user);_context.SaveChangesAsync();return(Task<IdentityResult>)Task.Delay(0);}publicvoidDispose(){// 何もしない}publicTask<SMSUser>FindByIdAsync(stringuserId,CancellationTokencancellationToken){return_context.SMSUsers.FirstOrDefaultAsync(u=>u.SMSUserId==userId,cancellationToken);}publicTask<SMSUser>FindByNameAsync(stringnormalizedUserName,CancellationTokencancellationToken){return_context.SMSUsers.FirstOrDefaultAsync(u=>u.SMSUserName.ToUpper()==normalizedUserName,cancellationToken);}publicTask<string>GetNormalizedUserNameAsync(SMSUseruser,CancellationTokencancellationToken){returnTask.Run(()=>user.SMSUserName.ToUpper(),cancellationToken);}publicTask<string>GetPasswordHashAsync(SMSUseruser,CancellationTokencancellationToken){returnTask.Run(()=>user.Password,cancellationToken);}publicTask<string>GetUserIdAsync(SMSUseruser,CancellationTokencancellationToken){returnTask.Run(()=>user.SMSUserId,cancellationToken);}publicTask<string>GetUserNameAsync(SMSUseruser,CancellationTokencancellationToken){returnTask.Run(()=>user.SMSUserName,cancellationToken);}publicTask<bool>HasPasswordAsync(SMSUseruser,CancellationTokencancellationToken){returnTask.Run(()=>string.IsNullOrEmpty(user?.Password),cancellationToken);}publicTaskSetNormalizedUserNameAsync(SMSUseruser,stringnormalizedName,CancellationTokencancellationToken){thrownewNotSupportedException("NormalizedUserName is not supported.");}publicTaskSetPasswordHashAsync(SMSUseruser,stringpasswordHash,CancellationTokencancellationToken){user.Password=passwordHash;return(Task<IdentityResult>)Task.Delay(0);}publicTaskSetUserNameAsync(SMSUseruser,stringuserName,CancellationTokencancellationToken){user.SMSUserName=userName;return(Task<IdentityResult>)Task.Delay(0);}publicTask<IdentityResult>UpdateAsync(SMSUseruser,CancellationTokencancellationToken){_context.SMSUsers.Update(user);_context.SaveChangesAsync();return(Task<IdentityResult>)Task.Delay(0);}}}これに伴い、DBContextも修正しています。
継承クラスをIdentityDbContext→DbContextに変更し、
SMSUsersを追加しました。
publicclassApplicationDbContext:DbContext{publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}publicDbSet<SalaryManagementSystem.Models.Salary>Salary{get;set;}publicDbSet<SalaryManagementSystem.Models.SMSUser>SMSUsers{get;set;}}最後にStartUp.csを修正します。
publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext<ApplicationDbContext>(options=>options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));services.AddDatabaseDeveloperPageExceptionFilter();//services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)// .AddEntityFrameworkStores<ApplicationDbContext>();services.AddIdentity<SMSUser,IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();services.AddControllersWithViews();}テーブル(SMSUser)がないので、作ります。
なぜかスキャフォールディングでモデルクラスとして選べなかったので、手動で作りました。
次回予告
長くなってきたので次回に持ち越します。
「あっこいつ間違っとるな…」と思ったらコメントで教えていただけるととても助かります…。


