情報応用演習Ⅰ(2024)

【T9a】簡易ブログソフトウェアの作成 Part.Ⅰ ~ 認証機能の組み込み(6/14)

プロジェクトタイプASP.NET Core Web アプリ(Model-View-Controller)
プロジェクト名T9a
ソリューション名PIT9
ターゲットフレームワーク.NET 8.0(長期的なサポート)
最上位レベルのステートメントを使用しない使用する(チェックオフ)
注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備作業を参照せよ.

9a-6. 初期ユーザー/グループのためのデータシーディング

次に前章でもやったように初期ユーザーとグループ(ロール)を作成するデータシーディングのためのクラスを作成しよう. 今回は事前に_に示す認証情報を作成することにする.

初期ユーザーとグループ(ロール)
名前種別備考
administratorsグループ(ロール)管理者グループ.AdminRoleName定数に格納する.
usersグループ(ロール)通常のユーザーグループ.NormalRoleName定数に格納する.
adminユーザー管理者ユーザー.AdminUserName定数に格納する.
初期パスワードは決め打ちとする.

プロジェクト内の Data フォルダを右クリックして「追加」→「クラス」をクリックする. IdentityDataSeeder(.csは省略可能)と入力して「追加」ボタンをクリックしよう. すると空のクラス定義が作られるので_の定義を書き込もう.

初期ユーザー作成のためのクラス
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using Microsoft.AspNetCore.Identity; // 追記
using T9a.Models;                    // 追記

namespace T9a.Data
{
    // 初期ユーザー作成のためのクラス
    public class IdentityDataSeeder
    {
        public const string AdminRoleName = "administrators";  // アプリケーション内で用いる管理者グループの名前(決め打ち)
        public const string AdminUserName = "admin";           // アプリケーション内で用いる管理者ユーザーの名前(決め打ち)
        public const string NormalRoleName = "users";          // アプリケーション内で用いる通常ユーザーのグループの名前(決め打ち)
        public const string AllRoles = "administrators,users"; // すべてのグループをカンマでつないだもの(認証の設定で使う)

        //                              ┌─ ここが前回と異なり IdentityUser ではなく 
        //                              │   BlogUser となっている点に注意
        //                              ↓
        private readonly UserManager<BlogUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;

        // コンストラクタ
        public IdentityDataSeeder(UserManager<BlogUser> userManager, RoleManager<IdentityRole> roleManager)
        {
            _userManager = userManager;
            _roleManager = roleManager;
        }

        // 初期ユーザー作成のためのメソッド(非同期メソッド版)
        public async Task EnsureDefaultCredentialsAsync()
        {
            if (!await _roleManager.RoleExistsAsync(AdminRoleName))                 // 管理者グループが存在していなければ
                await _roleManager.CreateAsync(new IdentityRole(AdminRoleName));    // それを作成する.
            if (!await _roleManager.RoleExistsAsync(NormalRoleName))                // 通常ユーザーのグループが存在していなければ
                await _roleManager.CreateAsync(new IdentityRole(NormalRoleName));   // それを作成する.

            var user = await _userManager.FindByNameAsync(AdminUserName);           // 管理者ユーザーが存在するかどうかを確認する.
            if (user == null)  // 存在しない場合.
            {
                user = new BlogUser()
                {
                    UserName = AdminUserName,
                    SecurityStamp = Guid.NewGuid().ToString("D"),
                    Nickname = AdminUserName,
                    Email = "admin@example.com",
                    Registered = DateTime.Now
                };

                await _userManager.CreateAsync(user, "p@55W0rD");                   // 管理者の初期パスワードは
                await _userManager.AddToRoleAsync(user, AdminRoleName);             // 決め打ちにする.
            }// if
        }

        // 初期ユーザー作成のためのメソッド(同期メソッド版)
        public void EnsureDefaultCredentials()
        {
            EnsureDefaultCredentialsAsync().Wait();
        }

        // Program.cs から呼び出して認証情報を作成するための静的メソッド
        public static void SeedData(IHost app)
        {
            var factory = app.Services.GetService<IServiceScopeFactory>();

            using (var scope = factory!.CreateScope())
            {
                var dataSeeder = scope.ServiceProvider.GetService<IdentityDataSeeder>();
                dataSeeder!.EnsureDefaultCredentials();
            }
        }
    }
}

前節とおおむね同じだが,違う部分はユーザーの管理のためのジェネリッククラスUserManager<TUser>のジェネリックパラメーターTUserIdentityUserではなく本節で定義したユーザークラスBlogUserを指定している点である.本節のように「ユーザー」として使用するクラスを 拡張する場合は,UserManager<TUser>ジェネリッククラスやSignInManager<TUser>ジェネリッククラスのTUserの部分には 自分で定義した「ユーザー」用のクラスを指定する必要がある.

次に, Program.cs に_に示す内容を追記しよう.

Program.csの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using KnzwTech.AspNetCore.ResourceBasedLocalization;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity; // 追記
using T9a.Data;
using T9a.Models;                    // 追記

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews(opt => opt.EnableDefaultErrorMessagesFromResource());

builder.Services.AddDbContext<T9aContext>(
    opt => opt.UseNpgsql(builder.Configuration.GetConnectionString(nameof(T9aContext))));

//                              ┌─ ここが前回と異なり IdentityUser ではなく 
//                              │   BlogUser となっている点に注意
//                              ↓
builder.Services.AddIdentity<BlogUser, IdentityRole>()
    .AddRoleManager<RoleManager<IdentityRole>>()
    .AddDefaultTokenProviders()
    .AddEntityFrameworkStores<T9aContext>();

builder.Services.ConfigureApplicationCookie(opt => {
    opt.LoginPath = "/Accounts/Login";
    opt.LogoutPath = "/Accounts/Logout";
    opt.AccessDeniedPath = "/Accounts/AccessDenied";
});

builder.Services.AddTransient<IdentityDataSeeder>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();

app.UseStatusCodePagesWithReExecute("/Home/AccessError/{0}");

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

IdentityDataSeeder.SeedData(app);

app.Run();
Last updated on 2024-06-19
Published on 2024-06-19

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.