开发环境:Asp.netCore 版本为3.1
创建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>
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
}
]
}
代码如下
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());
}
}
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(),
};
}
}
代码如下
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,
};
}
}
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;
}
}
}
}
}
}
}
}
访问 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结合可以很轻松的实现一个灵活的可配置的授权服务