public async Task <(string ClientId, string PlainSecret)> CreateAsync(ClientNewDto model) { if (await _clientRepo.IsExistedAsync(model.ClientUri)) { throw new IamException(HttpStatusCode.BadRequest, "该客户端地址已经存在!"); } ICollection <string> allowedCorsOrigins = null; if (model.AllowedCorsOrigins != null) { allowedCorsOrigins = model.AllowedCorsOrigins.Split(",", StringSplitOptions.RemoveEmptyEntries); foreach (var itm in allowedCorsOrigins) { if (!itm.IsUrl()) { throw new IamException(HttpStatusCode.BadRequest, $"{itm} 并不是合法的允许的跨域地址,必须是 Url 形式"); } } } model.ClientName = model.ClientName.Trim(); var allowedScopes = model.AllowedScopes?.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(sp => sp.Trim()).ToList() ?? new List <string>(); // 排除 id scopes 以及 已经存在的 api scopes var newScopes = allowedScopes.Except(await _clientRepo.GetIdentityResourceNamesAsync(allowedScopes)) .Except(await _clientRepo.GetApiResourceNamesAsync(allowedScopes)); _clientRepo.AddApiResources(newScopes); // 增加 IAM 需要的 scopes if (!allowedScopes.Contains("iam")) { allowedScopes.Add("iam"); } if (!allowedScopes.Contains("iamApi")) { allowedScopes.Add("iamApi"); } if (!allowedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId)) { allowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId); } if (!allowedScopes.Contains(IdentityServerConstants.StandardScopes.Profile)) { allowedScopes.Add(IdentityServerConstants.StandardScopes.Profile); } if (!allowedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess)) { allowedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess); } string clientId = Guid.NewGuid().ToString(); string secret = Helper.GetRandomString(30); string[] redirectUris = null; string[] postLogoutRedirectUris = null; if (String.IsNullOrWhiteSpace(model.RedirectUris)) { // signin-oidc 与 静默更新都是要作为回调的一部分 redirectUris = new[] { $"{model.ClientUri.TrimEnd('/')}/signin-oidc", $"{model.ClientUri.TrimEnd('/')}/silent-renew" }; } else { redirectUris = model.RedirectUris.Split(",", StringSplitOptions.RemoveEmptyEntries); } if (String.IsNullOrWhiteSpace(model.PostLogoutRedirectUris)) { postLogoutRedirectUris = new[] { $"{model.ClientUri.TrimEnd('/')}/signout-callback-oidc" }; } else { postLogoutRedirectUris = model.PostLogoutRedirectUris.Split(",", StringSplitOptions.RemoveEmptyEntries); } IdentityServer4.Models.Client client = new IdentityServer4.Models.Client { ClientId = clientId, ClientSecrets = { new Secret(secret.Sha256()) }, AlwaysSendClientClaims = model.AlwaysSendClientClaims, AlwaysIncludeUserClaimsInIdToken = model.AlwaysIncludeUserClaimsInIdToken, AccessTokenLifetime = model.AccessTokenLifetime, AllowedScopes = allowedScopes, AllowedCorsOrigins = allowedCorsOrigins, AllowedGrantTypes = GrantTypes.CodeAndClientCredentials, AllowOfflineAccess = true, // 允许使用 refresh token 来刷新(因为 Cookie 一般时间会大于 token 的有效期) ClientUri = model.ClientUri, ClientName = model.ClientName, Description = model.Description, Enabled = true, IdentityTokenLifetime = model.IdentityTokenLifetime, LogoUri = model.LogoUri, RedirectUris = redirectUris, PostLogoutRedirectUris = postLogoutRedirectUris, RequireConsent = false, RequireClientSecret = false, ClientClaimsPrefix = Constants.CLIENT_CLAIM_PREFIX, // 对于 Code 模式采用 Pkce 扩展 RequirePkce = true, Claims = new ClientClaim[] { // 每个 Client 都允许进行同步 perm 的操作 new ClientClaim(BuiltInPermissions.PERM_SYNC, BuiltInPermissions.PERM_SYNC) } }; _clientRepo.Add(client); //await _clientDbContext.SaveChangesAsync(); return(clientId, secret); }