ABP框架使用(版本3.3.1) - IdentityServer
1.IdentityServerClientScopes 分配的Scope太多,会报错
“Scope parameter exceeds max allowed length”
在Domain.Shared层改MyAppModuleExtensionConfigurator可以改abp identityserver定义的const值
1 2 3 4 | private static void ConfigureExistingProperties() { ClientScopeConsts.ScopeMaxLength = 1000; } |
这表里面字段长度,而Scope parameter validation是identityserver4的包里做的,这样改并没有效果
应该在Host层改变IdentityServerOptions , PreConfigureServices 和 PostConfigureServices 都可以
1 2 3 4 5 6 7 | public override void PostConfigureServices(ServiceConfigurationContext context) { PostConfigure<IdentityServerOptions>(options => { options.InputLengthRestrictions.Scope = 2000; }); } |
2.How to integrate Identity Server with Active Directory #2636
https://github.com/abpframework/abp/issues/2636
1 2 | context.Services.Replace(ServiceDescriptor .Transient<UserManager<IdentityUser>, LdapUserManager<IdentityUser>>()); |
public class LdapUserManager<TUser> : Microsoft.AspNetCore.Identity.UserManager<TUser> where TUser : IdentityUser { private readonly IEventService _events; private readonly ILdapManager _ldapManager; private readonly AbpLdapOptions _ldapOptions; public LdapUserManager( IUserStore<TUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<TUser> passwordHasher, IEnumerable<IUserValidator<TUser>> userValidators, IEnumerable<IPasswordValidator<TUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<TUser>> logger, IEventService events, ILdapManager ldapManager, IOptions<AbpLdapOptions> ldapOptions ) : base( store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger ) { _events = events; _ldapManager = ldapManager; _ldapOptions = ldapOptions.Value; } public override async Task<bool> CheckPasswordAsync(TUser user, string password) { if (string.IsNullOrEmpty(_ldapOptions.DomainName)) { throw new InvalidOperationException("The LDAP Hostname cannot be empty or null."); } if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(user.UserName)) { throw new InvalidOperationException("The LDAP User and Password cannot be empty or null."); } var userName = string.IsNullOrWhiteSpace(_ldapOptions.DomainName) ? user.UserName : $"{user.UserName}@{_ldapOptions.DomainName}"; var success = _ldapManager.Authenticate(userName, password); if (success) { Logger.LogInformation("Credentials validated for username: {username}", userName); await _events.RaiseAsync(new UserLoginSuccessEvent(userName, user.Id.ToString(), userName, interactive: false)); return true; } Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", userName); await _events.RaiseAsync(new UserLoginFailureEvent(userName, "invalid credentials", interactive: false)); return false; } //... }
3. Identity 接口
IIdentityServerInteractionService:用户交互相关接口
IResourceStore:获取资源接口:这里包括2中资源 一种是IdentityResource 和 ApiResource
IClientStore:获取客户端相关接口
IEventService:事件服务
UserStoreServices:自定义的用户服务,这里我没有用IdentityServer4的TestUserStore是为了方面自定义处理
4.证书 参考
IdentityServer4环境部署失败分析贴(一)
https://www.cnblogs.com/Imaigne/p/10519493.html
1 2 3 4 5 6 7 8 | //在正式环境中,这可能会报错 builder.AddDeveloperSigningCredential( true , "tempkey.rsa" ); //正确方式 builder.AddSigningCredential( new X509Certificate2(path, Configuration[ "Certificates:Password" ])) 这里可以参见郭的随笔: https: //www.cnblogs.com/guolianyu/p/9872661.html |
5.JWT与Reference Token的区别
IdentityServer4之JWT签名(RSA加密证书)及验签
https://www.cnblogs.com/guolianyu/p/9872661.html
6.OIDC
[认证 & 授权] 4. OIDC(OpenId Connect)身份认证(核心部分)
https://www.cnblogs.com/linianhui/p/openid-connect-core.html#auto-id-0
https://github.com/solenovex/Identity-Server-4-Tutorial-Demo-Code
7.Oauth2.0
OAuth2.0 知多少
https://www.cnblogs.com/sheng-jie/p/6564520.html
8.自定义登录认证,在domain层override IResourceOwnerPasswordValidator 的 ValidateAsync 方法
用 SignInManager.SignInWithClaimsAsync 可以加 Customer Claims
using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using IdentityModel; using IdentityServer4.AspNetIdentity; using IdentityServer4.Events; using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Validation; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.IdentityServer.Localization; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Volo.Abp.IdentityServer.AspNetIdentity { [Dependency(ReplaceServices = true)] public class MyAbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator, ITransientDependency { protected SignInManager<IdentityUser> SignInManager { get; } protected UserManager<IdentityUser> UserManager { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } protected ILogger<ResourceOwnerPasswordValidator<IdentityUser>> Logger { get; } protected IStringLocalizer<AbpIdentityServerResource> Localizer { get; } protected IHybridServiceScopeFactory ServiceScopeFactory { get; } protected AbpIdentityOptions AbpIdentityOptions { get; } public MyAbpResourceOwnerPasswordValidator( UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IdentitySecurityLogManager identitySecurityLogManager, ILogger<ResourceOwnerPasswordValidator<IdentityUser>> logger, IStringLocalizer<AbpIdentityServerResource> localizer, IOptions<AbpIdentityOptions> abpIdentityOptions, IHybridServiceScopeFactory serviceScopeFactory) { UserManager = userManager; SignInManager = signInManager; IdentitySecurityLogManager = identitySecurityLogManager; Logger = logger; Localizer = localizer; ServiceScopeFactory = serviceScopeFactory; AbpIdentityOptions = abpIdentityOptions.Value; } /// <summary> /// https://github.com/IdentityServer/IdentityServer4/blob/master/src/AspNetIdentity/src/ResourceOwnerPasswordValidator.cs#L53 /// </summary> /// <param name="context"></param> /// <returns></returns> [UnitOfWork] public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { var clientId = context.Request?.Client?.ClientId; using var scope = ServiceScopeFactory.CreateScope(); await ReplaceEmailToUsernameOfInputIfNeeds(context); IdentityUser user = null; var additionalClaims = new List<Claim>(); async Task SetSuccessResultAsync() { var sub = await UserManager.GetUserIdAsync(user); Logger.LogInformation("Credentials validated for username: {username}", context.UserName); await AddCustomClaimsAsync(additionalClaims, user, context); await SignInManager.SignInWithClaimsAsync(user, new AuthenticationProperties { IsPersistent = true }, additionalClaims); context.Result = new GrantValidationResult( sub, OidcConstants.AuthenticationMethods.Password, additionalClaims.ToArray() ); await IdentitySecurityLogManager.SaveAsync( new IdentitySecurityLogContext { Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, Action = IdentityServerSecurityLogActionConsts.LoginSucceeded, UserName = context.UserName, ClientId = clientId } ); } if (AbpIdentityOptions.ExternalLoginProviders.Any()) { foreach (var externalLoginProviderInfo in AbpIdentityOptions.ExternalLoginProviders.Values) { var externalLoginProvider = (IExternalLoginProvider)scope.ServiceProvider .GetRequiredService(externalLoginProviderInfo.Type); if (await externalLoginProvider.TryAuthenticateAsync(context.UserName, context.Password)) { user = await UserManager.FindByNameAsync(context.UserName); if (user == null) { user = await externalLoginProvider.CreateUserAsync(context.UserName, externalLoginProviderInfo.Name); } else { await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name); } await SetSuccessResultAsync(); return; } } } user = await UserManager.FindByNameAsync(context.UserName); string errorDescription = string.Empty; if (user != null) { AuthenticationProperties auth = new AuthenticationProperties(); var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true); if (result.Succeeded) { await SetSuccessResultAsync(); return; } else if (result.IsLockedOut) { Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName); errorDescription = Localizer["UserLockedOut"]; } else if (result.IsNotAllowed) { Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName); errorDescription = Localizer["LoginIsNotAllowed"]; } else { Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName); errorDescription = Localizer["InvalidUserNameOrPassword"]; } await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, Action = result.ToIdentitySecurityLogAction(), UserName = context.UserName, ClientId = clientId }); await SetSuccessResultAsync(); return; } else { Logger.LogInformation("No user found matching username: {username}", context.UserName); errorDescription = Localizer["InvalidUsername"]; await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, Action = IdentityServerSecurityLogActionConsts.LoginInvalidUserName, UserName = context.UserName, ClientId = clientId }); } context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription); } protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(ResourceOwnerPasswordValidationContext context) { if (!ValidationHelper.IsValidEmailAddress(context.UserName)) { return; } var userByUsername = await UserManager.FindByNameAsync(context.UserName); if (userByUsername != null) { return; } var userByEmail = await UserManager.FindByEmailAsync(context.UserName); if (userByEmail == null) { return; } context.UserName = userByEmail.UserName; } protected virtual Task AddCustomClaimsAsync(List<Claim> customClaims, IdentityUser user, ResourceOwnerPasswordValidationContext context) { if (user.TenantId.HasValue) { customClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString())); } customClaims.Add(new Claim(AbpClaimTypes.PhoneNumber,"testphone")); customClaims.Add(new Claim(JwtClaimTypes.Locale, "testLocale")); customClaims.Add(new Claim("testname", "testvalue")); return Task.CompletedTask; } } }
之后在AppService上可以用CurrentUser 获取 Customer Claims
AbpClaimTypes 可以在 configuration api 获取
{{baseUrl}}/api/abp/application-configuration
9. AuthenticationTypes.Federated vs. AuthenticationType Identity.Application
HttpContext.User.Identity.AuthenticationType == "Identity.Application"
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity( new List<Claim>{ new Claim(ClaimTypes.Name, username), new Claim(PartnersUserDataClaim, userData), new Claim(ModuleNameClaim, moduleName) }, "SSO/Windows")); await HttpContext.Authentication.SignInAsync(APIAuthSchemeName, principal);
然而这种方法我在另一个应用使用却有问题,Claim会被覆盖掉,而 AuthenticationType 是 "AuthenticationTypes.Federation"。
后来注入了AbpUserClaimsPrincipalFactory,还是不行。
然后试着在表 IdentityServerApiResources 把 新加的Claim Name加进去,就可以了。
public static readonly string DefaultAuthenticationType = "AuthenticationTypes.Federation";
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
IdentityServer4-master\IdentityServer4\src\IdentityServer4\src\Services\Default\DefaultClaimsService.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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using IdentityModel; using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Validation; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace IdentityServer4.Services { /// <summary> /// Default claims provider implementation /// </summary> public class DefaultClaimsService : IClaimsService { /// <summary> /// The logger /// </summary> protected readonly ILogger Logger; /// <summary> /// The user service /// </summary> protected readonly IProfileService Profile; /// <summary> /// Initializes a new instance of the <see cref="DefaultClaimsService"/> class. /// </summary> /// <param name="profile">The profile service</param> /// <param name="logger">The logger</param> public DefaultClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger) { Logger = logger; Profile = profile; } /// <summary> /// Returns claims for an identity token /// </summary> /// <param name="subject">The subject</param> /// <param name="resources">The requested resources</param> /// <param name="includeAllIdentityClaims">Specifies if all claims should be included in the token, or if the userinfo endpoint can be used to retrieve them</param> /// <param name="request">The raw request</param> /// <returns> /// Claims for the identity token /// </returns> public virtual async Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(ClaimsPrincipal subject, ResourceValidationResult resources, bool includeAllIdentityClaims, ValidatedRequest request) { Logger.LogDebug( "Getting claims for identity token for subject: {subject} and client: {clientId}" , subject.GetSubjectId(), request.Client.ClientId); var outputClaims = new List<Claim>(GetStandardSubjectClaims(subject)); outputClaims.AddRange(GetOptionalClaims(subject)); // fetch all identity claims that need to go into the id token if (includeAllIdentityClaims || request.Client.AlwaysIncludeUserClaimsInIdToken) { var additionalClaimTypes = new List< string >(); foreach ( var identityResource in resources.Resources.IdentityResources) { foreach ( var userClaim in identityResource.UserClaims) { additionalClaimTypes.Add(userClaim); } } // filter so we don't ask for claim types that we will eventually filter out additionalClaimTypes = FilterRequestedClaimTypes(additionalClaimTypes).ToList(); var context = new ProfileDataRequestContext( subject, request.Client, IdentityServerConstants.ProfileDataCallers.ClaimsProviderIdentityToken, additionalClaimTypes) { RequestedResources = request.ValidatedResources, ValidatedRequest = request }; await Profile.GetProfileDataAsync(context); var claims = FilterProtocolClaims(context.IssuedClaims); if (claims != null ) { outputClaims.AddRange(claims); } } else { Logger.LogDebug( "In addition to an id_token, an access_token was requested. No claims other than sub are included in the id_token. To obtain more user claims, either use the user info endpoint or set AlwaysIncludeUserClaimsInIdToken on the client configuration." ); } return outputClaims; } /// <summary> /// Returns claims for an identity token. /// </summary> /// <param name="subject">The subject.</param> /// <param name="resourceResult">The validated resource result</param> /// <param name="request">The raw request.</param> /// <returns> /// Claims for the access token /// </returns> public virtual async Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject, ResourceValidationResult resourceResult, ValidatedRequest request) { Logger.LogDebug( "Getting claims for access token for client: {clientId}" , request.Client.ClientId); var outputClaims = new List<Claim>() { new Claim(JwtClaimTypes.ClientId, request.ClientId) }; // log if client ID is overwritten if (! string .Equals(request.ClientId, request.Client.ClientId)) { Logger.LogDebug( "Client {clientId} is impersonating {impersonatedClientId}" , request.Client.ClientId, request.ClientId); } // check for client claims if (request.ClientClaims != null && request.ClientClaims.Any()) { if (subject == null || request.Client.AlwaysSendClientClaims) { foreach ( var claim in request.ClientClaims) { var claimType = claim.Type; if (request.Client.ClientClaimsPrefix.IsPresent()) { claimType = request.Client.ClientClaimsPrefix + claimType; } outputClaims.Add( new Claim(claimType, claim.Value, claim.ValueType)); } } } // add scopes (filter offline_access) // we use the ScopeValues collection rather than the Resources.Scopes because we support dynamic scope values // from the request, so this issues those in the token. foreach ( var scope in resourceResult.ScopeValues.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess)) { outputClaims.Add( new Claim(JwtClaimTypes.Scope, scope)); } // a user is involved if (subject != null ) { if (resourceResult.Resources.OfflineAccess) { outputClaims.Add( new Claim(JwtClaimTypes.Scope, IdentityServerConstants.StandardScopes.OfflineAccess)); } Logger.LogDebug( "Getting claims for access token for subject: {subject}" , subject.GetSubjectId()); outputClaims.AddRange(GetStandardSubjectClaims(subject)); outputClaims.AddRange(GetOptionalClaims(subject)); // fetch all resource claims that need to go into the access token var additionalClaimTypes = new List< string >(); foreach ( var api in resourceResult.Resources.ApiResources) { // add claims configured on api resource if (api.UserClaims != null ) { foreach ( var claim in api.UserClaims) { additionalClaimTypes.Add(claim); } } } foreach ( var scope in resourceResult.Resources.ApiScopes) { // add claims configured on scopes if (scope.UserClaims != null ) { foreach ( var claim in scope.UserClaims) { additionalClaimTypes.Add(claim); } } } // filter so we don't ask for claim types that we will eventually filter out additionalClaimTypes = FilterRequestedClaimTypes(additionalClaimTypes).ToList(); var context = new ProfileDataRequestContext( subject, request.Client, IdentityServerConstants.ProfileDataCallers.ClaimsProviderAccessToken, additionalClaimTypes.Distinct()) { RequestedResources = resourceResult, ValidatedRequest = request }; await Profile.GetProfileDataAsync(context); var claims = FilterProtocolClaims(context.IssuedClaims); if (claims != null ) { outputClaims.AddRange(claims); } } return outputClaims; } /// <summary> /// Gets the standard subject claims. /// </summary> /// <param name="subject">The subject.</param> /// <returns>A list of standard claims</returns> protected virtual IEnumerable<Claim> GetStandardSubjectClaims(ClaimsPrincipal subject) { var claims = new List<Claim> { new Claim(JwtClaimTypes.Subject, subject.GetSubjectId()), new Claim(JwtClaimTypes.AuthenticationTime, subject.GetAuthenticationTimeEpoch().ToString(), ClaimValueTypes.Integer64), new Claim(JwtClaimTypes.IdentityProvider, subject.GetIdentityProvider()) }; claims.AddRange(subject.GetAuthenticationMethods()); return claims; } /// <summary> /// Gets additional (and optional) claims from the cookie or incoming subject. /// </summary> /// <param name="subject">The subject.</param> /// <returns>Additional claims</returns> protected virtual IEnumerable<Claim> GetOptionalClaims(ClaimsPrincipal subject) { var claims = new List<Claim>(); var acr = subject.FindFirst(JwtClaimTypes.AuthenticationContextClassReference); if (acr != null ) claims.Add(acr); return claims; } /// <summary> /// Filters out protocol claims like amr, nonce etc.. /// </summary> /// <param name="claims">The claims.</param> /// <returns></returns> protected virtual IEnumerable<Claim> FilterProtocolClaims(IEnumerable<Claim> claims) { var claimsToFilter = claims.Where(x => Constants.Filters.ClaimsServiceFilterClaimTypes.Contains(x.Type)); if (claimsToFilter.Any()) { var types = claimsToFilter.Select(x => x.Type); Logger.LogDebug( "Claim types from profile service that were filtered: {claimTypes}" , types); } return claims.Except(claimsToFilter); } /// <summary> /// Filters out protocol claims like amr, nonce etc.. /// </summary> /// <param name="claimTypes">The claim types.</param> protected virtual IEnumerable< string > FilterRequestedClaimTypes(IEnumerable< string > claimTypes) { var claimTypesToFilter = claimTypes.Where(x => Constants.Filters.ClaimsServiceFilterClaimTypes.Contains(x)); return claimTypes.Except(claimTypesToFilter); } } } |
10.AbpUserClaimsPrincipalFactory
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 | using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; using IdentityRole = Volo.Abp.Identity.IdentityRole; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace DRS.Identity { [Dependency(ReplaceServices = true )] [ExposeServices( typeof (AbpUserClaimsPrincipalFactory))] // 替换旧的AbpUserClaimsPrincipalFactory public class MyUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory, IScopedDependency { public MyUserClaimsPrincipalFactory( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options) : base ( userManager, roleManager, options) { } public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user) { var principal = await base .CreateAsync(user); var identityPrincipal = principal.Identities.First(); /// add custom claim identityPrincipal.AddClaim( new Claim( "fff" , "ddddd" )); return principal; } } } |
11. Permission 分为 Client 和 Role
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 | GO /****** Object: StoredProcedure [dbo].[AddAPIScope] Script Date : 2021/1/29 22:53:11 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [dbo].[AddAPIScope] @scope nvarchar(500), @client nvarchar(500)= 'TEST_APP' AS BEGIN -- exec AddAPIScope 'DRS.TEST','TEST_APP' select @scope,@client merge into AbpPermissionGrants p using( select newid() as id, null as tenantid,@scope as name , 'R' as providername, 'admin' as providerkey) as t on (p. name =t. name and p.providername=t.providername and p.providerkey=t.providerkey) when not matched then insert (id,tenantid, name ,providername,providerkey) values (t.id,t.tenantid,t. name ,t.providername,t.providerkey); merge into AbpPermissionGrants p using( select newid() as id, null as tenantid,@scope as name , 'C' as providername,@client as providerkey) as t on (p. name =t. name and p.providername=t.providername and p.providerkey=t.providerkey) when not matched then insert (id,tenantid, name ,providername,providerkey) values (t.id,t.tenantid,t. name ,t.providername,t.providerkey); merge into IdentityServerApiScopes p using( select id as apiresourceid,@scope as name ,@scope+ ' API' as displayname, null as description,0 as required,0 as emphasize,1 as showindiscoverydocument from [dbo].[IdentityServerApiResources] where name = 'TEST' ) as t on (p.apiresourceid =t.apiresourceid and p. name =t. name ) when not matched then insert (apiresourceid, name ,displayname,description,required,emphasize,showindiscoverydocument) values (t.apiresourceid,t. name ,t.displayname,t.description,t.required,t.emphasize,t.showindiscoverydocument); merge into IdentityServerClientScopes p using( select id as clientid,@scope as scope from [dbo].[IdentityServerClients] where ClientId = @client) as t on (p.clientid =t.clientid and p.scope=t.scope) when not matched then insert (clientid,scope) values (t.clientid,t.scope); select * from [AbpPermissionGrants] where name = @scope select * from [IdentityServerApiScopes] where name = @scope select c.ClientId,cs.* from [IdentityServerClients] c,[IdentityServerClientScopes] cs where c.id=cs.ClientId and scope = @scope END |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!