IdentityServer4整合Asp.netCore Identity

李胤
2023-12-01

前言

开发环境:Asp.netCore 版本为3.1

1.创建Web项目,并添加项目引用

	创建web项目
	dotnet new web
	
	编辑项目文件,填加nuget包引用
<ItemGroup>
    <PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.0.4" />
    <PackageReference Include="IdentityServer4.EntityFramework" Version="4.0.4" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.7" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
    <PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="3.1.0" />
  </ItemGroup>

2.创建一个json文件,用于配置Client,Api资源

id4.json 配置如下

{
  //客户端
  "Clients": [
    {
      "ClientId": "wpf",
      "ClientSecrets": [
        { "Value": "wpf_secret" }
      ],
      "AllowedGrantTypes": [ "client_credentials" ],
      "Enabled": "true",
      "AllowedScopes": [ "system", "mes" ]
    },
    {
      "ClientId": "web",
      "ClientSecrets": [
        { "Value": "web_secret" }
      ],
      "AllowedGrantTypes": [ "password" ],
      "Enabled": "true",
      "AllowedScopes": [ "openid", "profile", "system", "mes" ]
    }
  ],
  //api接口配置
  "ApiScopes": [
    {
      "Name": "system",
      "DisplayName": "系统管理服务",
      "Enabled": true
    },
    {
      "Name": "mes",
      "DisplayName": "mes系统服务",
      "Enabled": true
    }
  ]
}

3.创建ApplicationDbContext 继承自 IdentityDbContext 并创建用户,角色等实体类,重写IdentityUser,IdentityRole

代码如下

public class ApplicationDbContext : IdentityDbContext<SysUser, SysRole, string, SysUserClaim, SysUserRole, SysUserLogin, SysRoleClaim, SysUserToken>
    {
        /// <summary>
        /// 组织机构
        /// </summary>
        public DbSet<SysOrgUnit> OrgUnit { get; set; }
        /// <summary>
        /// 系统菜单
        /// </summary>
        public DbSet<SysMenuMaster> MenuMaster { get; set; }
        /// <summary>
        /// 系统菜单(定制化)
        /// </summary>
        public DbSet<SysMenu> Menu { get; set; }
        /// <summary>
        /// 部门
        /// </summary>
        public DbSet<SysDepartment> Department { get; set; }
        /// <summary>
        /// 部门路径
        /// </summary>
        public string Path { get; set; }
        /// <summary>
        /// 层级
        /// </summary>
        public int Level { get; set; }



        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            builder.Entity<SysUser>().ToTable("SysUser");
            builder.Entity<SysUserClaim>().ToTable("SysUserClaim");
            builder.Entity<SysUserLogin>().ToTable("SysUserLogin");
            builder.Entity<SysUserToken>().ToTable("SysUserToken");
            builder.Entity<SysUserRole>().ToTable("SysUserRole");
            builder.Entity<SysUserRole>()
                .HasOne(u => u.Role).WithMany().HasForeignKey(u => u.RoleId);
            builder.Entity<SysUserRole>()
               .HasOne(u => u.User).WithMany().HasForeignKey(u => u.UserId);
            builder.Entity<SysUserRole>().ToTable("SysUserRole");
            builder.Entity<SysRole>().ToTable("SysRole");
            builder.Entity<SysRoleClaim>().ToTable("SysRoleClaim");
            builder.Entity<SysOrgUnit>().ToTable("SysOrgUnit");
            builder.Entity<SysMenuMaster>().ToTable("SysMenuMaster");
            builder.Entity<SysMenu>().ToTable("SysMenu");
            builder.Entity<SysDepartment>().ToTable("SysDepartment");
            builder.Entity<SysRole>().HasData(DatabaseIniter.GetRoles());
            builder.Entity<SysDepartment>().HasData(DatabaseIniter.GetDepartments());
            builder.Entity<SysOrgUnit>().HasData(DatabaseIniter.GetOrgUnits());
        }
    }

4. 创建一个Config.cs 用于读取id4的配置

	internal class Config
    {
        static IConfigurationRoot _configuration;
        static Config()
        {
            string dir = Directory.GetCurrentDirectory();
            _configuration = new ConfigurationBuilder().AddJsonFile("id4.json")
                .SetBasePath(dir)
                .Build();
        }
        internal static IEnumerable<Client> GetClients()
        {
            var clients = _configuration.GetSection("Clients").Get<List<Client>>();
            foreach (Client client in clients)
            {
                foreach (var secret in client.ClientSecrets)
                {
                    secret.Value = secret.Value.Sha256();
                }
            }
            return clients;
        }
        internal static IEnumerable<ApiScope> GetApiScopes()
        {
            var scopes = _configuration.GetSection("ApiScopes").Get<List<ApiScope>>();
            return scopes;
        }
        internal static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            };
        }

    }




5 创建MyProfileService 用于配置授权信息

代码如下

  internal class MyProfileService : IProfileService
    {
        private readonly ApplicationDbContext _context;
        private readonly IUserClaimsPrincipalFactory<SysUser> _userClaimsPrincipalFactory;


        public MyProfileService(ApplicationDbContext context,
            UserClaimsPrincipalFactory<SysUser> userClaimsPrincipalFactory)
        {
            _context = context;
            _userClaimsPrincipalFactory = userClaimsPrincipalFactory;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            string userId = context.Subject.GetSubjectId();
            SysUser currentUser = _context.Users.Find(userId);
            List<Claim> claims = new List<Claim>();
            var principal = await _userClaimsPrincipalFactory.CreateAsync(currentUser);
            claims.AddRange(principal.Claims);
            claims.Add(new Claim(Consts.ClaimTypes.ClientId, context.Client.ClientId));
            var roles = GetRoles(currentUser.Id);
            var rids = roles.Select(u => u.Id).ToList();
            var roleClaims = _context.RoleClaims.Where(u => u.ClaimType == Consts.ClaimTypes.RoleAccess && rids.Any(r => r == u.RoleId)).Select(u => u.ClaimValue).ToList();

            UserData userData = new UserData
            {
                UserId = currentUser.Id,
                UserName = currentUser.UserName,
                RealName = currentUser.RealName,
                Email = currentUser.Email,
                Photo = currentUser.Photo,
                UnitId = currentUser.OrgUnitId,
                IsAdmin = currentUser.IsAdmin,
                Roles = roles,
                RoleAccesses = roleClaims,
                UserAccesses = principal.Claims.Where(u => u.Type == Consts.ClaimTypes.UserAccess).Select(u => u.Value).ToList(),
                ClientId = context.Client.ClientId
            };
            if (!currentUser.DepartmentId.IsNullOrEmpty())
            {
                var dept = _context.Department.Include(u => u.Children).FirstOrDefault(u => u.Id == currentUser.DepartmentId);
                if (dept != null)
                {
                    userData.Department = GetDepartmentInfo(dept);
                    userData.LowDepartments = GetDepartmentInfo(dept.Children);
                }
            }
            claims.Add(new Claim(ClaimTypes.UserData, Newtonsoft.Json.JsonConvert.SerializeObject(userData)));
            context.IssuedClaims.AddRange(claims);
        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            string id = context.Subject.GetSubjectId();
            var user = await _context.Users.FindAsync(id);
            if (user != null && !user.IsDeleted&&!string.IsNullOrEmpty(user.UnitId))
            {
                context.IsActive = true;
                return;
            }
            context.IsActive = false;
        }


        private List<RoleInfo> GetRoles(string userId)
        {
            return _context.UserRoles.Include(u => u.Role)
            .Where(u => u.UserId == userId && !u.Role.IsDeleted)
            .Select(u => new RoleInfo
            {
                Id = u.RoleId,
                RoleName = u.Role.Name
            }).ToList();
        }
        /// <summary>
        /// 获取下级部门信息
        /// </summary>
        /// <param name="children"></param>
        /// <returns></returns>
        private List<DepartmentInfo> GetDepartmentInfo(ICollection<SysDepartment> children)
        {
            List<DepartmentInfo> list = new List<DepartmentInfo>();
            if (children.Any())
            {
                foreach (var item in children)
                {
                    list.Add(GetDepartmentInfo(item));
                    if (item.Children != null)
                    {
                        list.AddRange(GetDepartmentInfo(item.Children));
                    }
                }
            }
            return list;
        }
        /// <summary>
        /// 查询部门信息
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private static DepartmentInfo GetDepartmentInfo(SysDepartment item)
        {
            return new DepartmentInfo
            {
                Id = item.Id,
                Level = item.Level,
                DepartmentName = item.Name,
                ParentId = item.ParentId,
            };
        }


    }

6 在Startup.cs中进行配置

 public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddDbContext<ApplicationDbContext>(u => u.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
            services.AddIdentity<SysUser, SysRole>(o =>
            {
                o.User.RequireUniqueEmail = true;
                o.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@";
                o.Password.RequireDigit = false;
                o.Password.RequireLowercase = false;
                o.Password.RequireNonAlphanumeric = false;
                o.Password.RequireUppercase = false;
                o.Password.RequiredLength = 5;
            })
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            services.AddScoped<IUserClaimsPrincipalFactory<SysUser>, UserClaimsPrincipalFactory<SysUser>>();
            services.AddScoped<IProfileService, MyProfileService>();
            services.AddIdentityServer()
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryClients(Config.GetClients())
                .AddInMemoryApiScopes(Config.GetApiScopes())
                .AddAspNetIdentity<SysUser>()
                .AddProfileService<MyProfileService>()
                .AddDeveloperSigningCredential();
            services.AddControllers();
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            loggerFactory.AddLog4Net();
            app.UseIdentityServer();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
            Task.Run(() => InitDatabase(app));
        }

        private void InitDatabase(IApplicationBuilder app)
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                using (var ctx = scope.ServiceProvider.GetService<ApplicationDbContext>())
                {
                    if (ctx.Database.EnsureCreated())
                    {
                        using (var userManager = scope.ServiceProvider.GetService<UserManager<SysUser>>())
                        {
                            if (!userManager.Users.Any())
                            {
                                foreach (var user in DatabaseIniter.GetUsers())
                                {
                                    var result = userManager.CreateAsync(user, Consts.DefaultUserPassword).Result;
                                }
                            }
                        }
                    }
                }

            }

        }
    }

6 配置完毕,启动项目

访问 http://localhost:5000/.well-known/openid-configuration,如出现以下内容表示配置成功

{
    "issuer": "http://localhost:5000",
    "jwks_uri": "http://localhost:5000/.well-known/openid-configuration/jwks",
    "authorization_endpoint": "http://localhost:5000/connect/authorize",
    "token_endpoint": "http://localhost:5000/connect/token",
    "userinfo_endpoint": "http://localhost:5000/connect/userinfo",
    "end_session_endpoint": "http://localhost:5000/connect/endsession",
    "check_session_iframe": "http://localhost:5000/connect/checksession",
    "revocation_endpoint": "http://localhost:5000/connect/revocation",
    "introspection_endpoint": "http://localhost:5000/connect/introspect",
    "device_authorization_endpoint": "http://localhost:5000/connect/deviceauthorization",
    "frontchannel_logout_supported": true,
    "frontchannel_logout_session_supported": true,
    "backchannel_logout_supported": true,
    "backchannel_logout_session_supported": true,
    "scopes_supported": [
        "openid",
        "profile",
        "system",
        "mes",
        "offline_access"
    ],
    "claims_supported": [
        "sub",
        "name",
        "family_name",
        "given_name",
        "middle_name",
        "nickname",
        "preferred_username",
        "profile",
        "picture",
        "website",
        "gender",
        "birthdate",
        "zoneinfo",
        "locale",
        "updated_at"
    ],
    "grant_types_supported": [
        "authorization_code",
        "client_credentials",
        "refresh_token",
        "implicit",
        "password",
        "urn:ietf:params:oauth:grant-type:device_code"
    ],
    "response_types_supported": [
        "code",
        "token",
        "id_token",
        "id_token token",
        "code id_token",
        "code token",
        "code id_token token"
    ],
    "response_modes_supported": [
        "form_post",
        "query",
        "fragment"
    ],
    "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "subject_types_supported": [
        "public"
    ],
    "code_challenge_methods_supported": [
        "plain",
        "S256"
    ],
    "request_parameter_supported": true
}

总结

IdentityServer4 和AspNetCore Identity结合可以很轻松的实现一个灵活的可配置的授权服务

 类似资料: