IdentityServer4: 集成 AspNetCore Identity 框架

IdentityServer4 集成 AspNetCore Identity 框架

本节基于 IdentityServer4: 配置项持久化 一节的代码基础上进行。

新增依赖包

在 IdentityServer4 项目:Dotnet.WebApi.Ids4.AuthService 中新增集成 AspNetCore Identity 所需的依赖包:

    <PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.14" />

最终需要的依赖包为:

    <PackageReference Include="IdentityServer4" Version="4.1.2" />
    <PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
    <PackageReference Include="IdentityServer4.EntityFramework" Version="4.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.14" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3">

集成 AspNetIdentity 代码

           //获取数据库连接字符串,从appsettings.json中读取。
            var connetionString = builder.Configuration.GetConnectionString("Ids4Connection");

            //注册AppDbContext服务。
            builder.Services.AddDbContext<AppDbContext>(options =>
            {
                options.UseSqlServer(connetionString);
            });

            //注册Asp.Net Core Identity服务。
            builder.Services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<AppDbContext>()
                .AddDefaultTokenProviders();

            var ids4Builder = builder.Services.AddIdentityServer().            
                .AddAspNetIdentity<IdentityUser>();  //使用持久化用户

迁移 AspNetIdentity 数据库

控制台上输入如下命令生成迁移代码:

Add-Migration InitIdentity -Context AppDbContext -o Data\Migrations\Ids4\AspNetIdentity

再使用命令执行生成的迁移代码,在 SQL Server 数据库中创建表结构:

Update-Database -Context AppDbContext

迁移成功后,数据库表会新增几张以 AspNet 开头的表,如下图所示:

注意:ASP.NET Core Identity 框架中的表很多,从上图中,可以看到只是将与用户相关的表创建出来了。

生成用户信息

编写一个类,用于初始化用户信息,在项目的根目录添加 SyncUsers.cs 类,然后编写如下代码:

    public class SyncUsers
    {
        public static async void InitDbUser(IApplicationBuilder app)
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
                context.Database.Migrate();

                var userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();

                var username = "zhangsan";
                var userToAdd = await userManager.FindByNameAsync(username);
                if (userToAdd == null)
                {
                    IdentityUser user = new IdentityUser()
                    {
                        Id = Guid.NewGuid().ToString(),
                        UserName = username,
                        PhoneNumber = "12345678911"
                    };
                    var result = userManager.CreateAsync(user, "1q2w3E*").Result;

                    if(result.Succeeded)
                    {
                         var claims = new List<Claim>
                        {
                            new Claim(JwtClaimTypes.Name, "Zhangsan"),
                            new Claim(JwtClaimTypes.GivenName, "Zs"),
                            new Claim(JwtClaimTypes.FamilyName, "Zhang"),
                            new Claim(JwtClaimTypes.Email, "zhangsan@dotnet.com"),
                            new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
                        };

                        result = await userManager.AddClaimsAsync(user, claims);
                    }
        
                    if (result.Succeeded)
                    {
                        Console.WriteLine("初始化用户成功。");
                    }else
                    {
                        Console.WriteLine("初始化用户失败!!!");
                    }
                }
    
            }
        }
    }

将 SyncUsers.InitDbUser()方法放在 Program 类中去执行,实现用户信息的添加:

            var app = builder.Build();

            SyncUsers.InitDbUser(app);

修改 IdentityServer.QuickstartUI 代码

登录

找到 Quickstart/Account/AccountController.cs,添加如下代码:

    public class AccountController : Controller
    {
        ......
        private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<IdentityUser> _signInManager;

        public AccountController(
            ......
            UserManager<IdentityUser> userManager,
            SignInManager<IdentityUser> signInManager,
           )
        {

       [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginInputModel model, string button)
        {
            // check if we are in the context of an authorization request
            var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);

            // the user clicked the "cancel" button
            if (button != "login")
            {
                if (context != null)
                {
                    // if the user cancels, send a result back into IdentityServer as if they 
                    // denied the consent (even if this client does not require consent).
                    // this will send back an access denied OIDC error response to the client.
                    await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);

                    // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                    if (context.IsNativeClient())
                    {
                        // The client is native, so this change in how to
                        // return the response is for better UX for the end user.
                        return this.LoadingPage("Redirect", model.ReturnUrl);
                    }

                    return Redirect(model.ReturnUrl);
                }
                else
                {
                    // since we don't have a valid context, then we just go back to the home page
                    return Redirect("~/");
                }
            }

            if (ModelState.IsValid)
            {
                //获取用户对象
                var user = await _userManager.FindByNameAsync(model.Username);
                if (user != null)
                {
                    //登录
                    var r = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true);
                    if (r.Succeeded)
                    {
                        await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName));
                        if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl))
                        {
                            return Redirect(model.ReturnUrl);
                        }
                        return Redirect("~/");
                    }
                }
                await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId: context?.Client.ClientId));
                ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
            }

            // something went wrong, show form with error
            var vm = await BuildLoginViewModelAsync(model);
            return View(vm);
        }

退出

找到 Quickstart/Account/AccountController.cs,找到Logout() 方法, 替换如下代码

public async Task<IActionResult> Logout(LogoutInputModel model)
{
      ......
-     await HttpContext.SignOutAsync();
+     await _signInManager.SignOutAsync();
      ......
}

使用 IdentityUser 用户登录

使用初始化用户 zhangsan,密码:1q2w3e* 登录,得到 AccessToken:

eyJhbGciOiJSUzI1NiIsImtpZCI6IjQzMjE3QzNGNzY1MEVGRUIyNTU4RTNERjc2REY4QzJDRTVEQ0ZDNkFSUzI1NiIsInR5cCI6ImF0K2p3dCIsIng1dCI6IlF5RjhQM1pRNy1zbFdPUGZkdC1NTE9YY19HbyJ9.eyJuYmYiOjE2NzgzMzQ5OTksImV4cCI6MTY3ODMzODU5OSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwMSIsImNsaWVudF9pZCI6Ik12Y0FwcCIsInN1YiI6ImNlYzYyMWJlLWJkOWYtNDdjNi05NjQ2LWQxOGE5ZjQ5ZmJlMCIsImF1dGhfdGltZSI6MTY3ODMzNDk5NCwiaWRwIjoibG9jYWwiLCJqdGkiOiJBNDJBRkQ4QUY1QzBDMTNEMzEzQUI0QTdDMDJGQzdFOCIsInNpZCI6Ijc2MTIxOUNERDIwMkJDNDI5NzZDMEZCNDEzRUNENTBEIiwiaWF0IjoxNjc4MzM0OTk5LCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiT0FBUEkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicHdkIl19.prQrI-B_QrZy6js3bg32HzFPCZZy2u8WWjqRLCZw24QmzeksCaY4-cPGo_zeCl4TNKkSFoLMUIRGO4OkMd2agDhQm_xyzw8RajrE8WA0nBHDZSsHXtwXPQDSpy8i2kVxEZQ31U_PjEulBIhzyrluQwMjXFsziRBUrTS6ZFMReduExwlMZZHu_bzVAyQOFu2oARjfGO3-6IYfHbsVXrbNVjCw47FQazgQMJU190OL3SoHhY9BsKnAHZ2RgQ-RnfFOtcfOxMvZz9dgXjLsSwi-bzmfE54PsCWcwne2Fau9WsWQNVOz3u1TVeToW5cF0ytN1k_qsx1TQAvRVzmTvWPaOA

打开 https://jwt.io, 可以解析到 AccessToken 的 PAYLOAD:DATA 信息:

{
  "nbf": 1678334999,
  "exp": 1678338599,
  "iss": "https://localhost:6001",
  "client_id": "MvcApp",
  "sub": "cec621be-bd9f-47c6-9646-d18a9f49fbe0",
  "auth_time": 1678334994,
  "idp": "local",
  "jti": "A42AFD8AF5C0C13D313AB4A7C02FC7E8",
  "sid": "761219CDD202BC42976C0FB413ECD50D",
  "iat": 1678334999,
  "scope": [
    "openid",
    "profile",
    "OAAPI",
    "offline_access"
  ],
  "amr": [
    "pwd"
  ]
}
posted @ 2023-03-09 19:32  easy5  阅读(370)  评论(11编辑  收藏  举报