Identityserver4 如何正确保护STS Asp.Net 2.2项目中的API+;Api Asp.Net 2.2项目+;angular7客户端

Identityserver4 如何正确保护STS Asp.Net 2.2项目中的API+;Api Asp.Net 2.2项目+;angular7客户端,identityserver4,Identityserver4,已经为此挣扎了一段时间,让angular7客户端访问id4 Asp.Net core 2.2项目以登录并获取jwt没有问题,让angular7客户端访问受id4保护的Asp.Net core 2.2 api项目保护的api也没有问题 jwt.io解码(值x'd out): ID4配置: private static readonly string[] customClaimTypes = { "role", "jseg", "jobid", "regid", "api1" };

已经为此挣扎了一段时间,让angular7客户端访问id4 Asp.Net core 2.2项目以登录并获取jwt没有问题,让angular7客户端访问受id4保护的Asp.Net core 2.2 api项目保护的api也没有问题

jwt.io解码(值x'd out):

ID4配置:

private static readonly string[] customClaimTypes = { "role", "jseg", "jobid", "regid", "api1" };

        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResource("api1scope", customClaimTypes),
            };
        }

        public static IEnumerable<ApiResource> GetApis()
        {
            return new ApiResource[]
            {
                new ApiResource()
                {
                    Name = "api1",
                    Description = "tsicApis",
                    ApiSecrets =
                    {
                        new Secret(Startup.Configuration.GetSection("StsConfig:STSTSICApisSecuredSecret").Value.Sha256())
                    },
                    Scopes =
                    {
                        new Scope()
                        {
                            Name = "api1",
                            DisplayName = "Scope for the api1 ApiResource",
                        },
                    },
                    UserClaims = customClaimTypes
                }
            };
        }

        // clients want to access resources (aka scopes)
        public static IEnumerable<Client> GetClients()
        {
            var trustedClientSecrets = Startup.Configuration.GetSection("StsConfig:TrustedClientSecrets").Value;

            var angularClientUrl = Startup.Configuration.GetSection("StsConfig:AngularClientUrl").Value;
            var angularRedirectUris = Startup.Configuration.GetSection("StsConfig:AngularRedirectUris").Value;
            var angularPostLogoutRedirectUris = Startup.Configuration.GetSection("StsConfig:AngularPostLogoutRedirectUris").Value;
            var angularAllowedCorsOrigins = Startup.Configuration.GetSection("StsConfig:AngularAllowedCorsOrigins").Value;
            var angularClientSecret = Startup.Configuration.GetSection("StsConfig:STSTSICApisSecuredSecret").Value;

            var mvcClientSecrets = Startup.Configuration.GetSection("StsConfig:MVCClientSecrets").Value;
            var mvcRedirectUris = Startup.Configuration.GetSection("StsConfig:MVCRedirectUris").Value;
            var mvcFrontChannelLogoutUri = Startup.Configuration.GetSection("StsConfig:MVCFrontChannelLogoutUri").Value;
            var mvcPostLogoutRedirectUris = Startup.Configuration.GetSection("StsConfig:MVCPostLogoutRedirectUris").Value;


            // client credentials client
            return new List<Client>
            {
                new Client
                {
                    ClientName = "angularclient",
                    ClientId = "angularclient",
                    RequireClientSecret = true,
                    ClientSecrets = { new Secret(angularClientSecret) },

                    RequireConsent = true,
                    AllowRememberConsent = false,

                    AccessTokenType = AccessTokenType.Jwt,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    AccessTokenLifetime = 33000,// 330 seconds, default 60 minutes
                    IdentityTokenLifetime = 3000,
                    AllowAccessTokensViaBrowser = true,

                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowedCorsOrigins = angularAllowedCorsOrigins.Split(','),
                    AllowedScopes =
                    {
                        "openid",
                        "profile",
                        "email",
                        "role",
                        "jseg",
                        "jobid",
                        "regid",
                        "api1",
                        "api1scope",
                    },

                    RedirectUris = angularRedirectUris.Split(','),
                    PostLogoutRedirectUris = angularPostLogoutRedirectUris.Split(',')
                },
                new Client
                {
                    ClientId = "mvcclient",
                    ClientName = "mvcclient",

                    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
                    ClientSecrets = { new Secret(mvcClientSecrets.Sha256()) },

                    RequireConsent = true,
                    AllowRememberConsent = false,

                    RedirectUris = mvcRedirectUris.Split(','),
                    FrontChannelLogoutUri = mvcFrontChannelLogoutUri,
                    PostLogoutRedirectUris = mvcPostLogoutRedirectUris.Split(','),

                    AllowOfflineAccess = true,
                    AllowedScopes = new List<string>
                    {
                        "openid",
                        "profile",
                        "api1"
                    }
                },

            };
        }
Asp.Net core 2.2 api项目start.cs:

services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("IdentityServer4Strings:Authority");
                    options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer4Strings:RequireHttpsMetadata");
                    options.ApiName = Configuration.GetValue<string>("IdentityServer4Strings:ApiName");

                    options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
                    options.ApiSecret = Configuration.GetValue<string>("IdentityServer4Strings:STSTSICApisSecuredSecret");
                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
                });


Asp.Net core 2.2 sts project start.cs:

        services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

        var identityServer = services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
        })
            .AddProfileService<IdentityWithAdditionalClaimsProfileService>()
            //.AddTestUsers(TestUsers.Users)
            // this adds the config data from DB (clients, resources, CORS)
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString);
            })
            // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString);

                // this enables automatic token cleanup. this is optional.
                options.EnableTokenCleanup = true;
            })
            .AddProfileService<IdentityWithAdditionalClaimsProfileService>()
            .AddAspNetIdentity<ApplicationUser>();


        services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();



Api authorize protection decorator (have tried both):
`
        //[Authorize]
        [Authorize(AuthenticationSchemes = "Bearer")]
`

Asp.Net core 2.2 sts start.cs:
`
            services.AddIdentity<ApplicationUser, IdentityRole>()
                    .AddEntityFrameworkStores<ApplicationDbContext>()
                    .AddDefaultTokenProviders();

            var identityServer = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            })
                .AddProfileService<IdentityWithAdditionalClaimsProfileService>()
                //.AddTestUsers(TestUsers.Users)
                // this adds the config data from DB (clients, resources, CORS)
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString);
                })
                // this adds the operational data from DB (codes, tokens, consents)
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString);

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
                })
                .AddProfileService<IdentityWithAdditionalClaimsProfileService>()
                .AddAspNetIdentity<ApplicationUser>();


            services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();
启动Asp.Net Core 2.2 sts项目时,我得到:

Seeding database...
Clients already populated
IdentityResources already populated
ApiResources already populated
Done seeding database.

[09:28:09 Information] IdentityServer4.Startup
Starting IdentityServer4 version 2.3.2.0

[09:28:09 Information] IdentityServer4.Startup
Using the default authentication scheme Identity.Application for IdentityServer

[09:28:09 Debug] IdentityServer4.Startup
Using Identity.Application as default ASP.NET Core scheme for authentication

[09:28:09 Debug] IdentityServer4.Startup
Using Identity.External as default ASP.NET Core scheme for sign-in

[09:28:09 Debug] IdentityServer4.Startup
Using Identity.External as default ASP.NET Core scheme for sign-out

[09:28:09 Debug] IdentityServer4.Startup
Using Identity.Application as default ASP.NET Core scheme for challenge

[09:28:09 Debug] IdentityServer4.Startup
Using Identity.Application as default ASP.NET Core scheme for forbid

[09:28:10 Debug] IdentityServer4.EntityFramework.TokenCleanup
Starting grant removal

Hosting environment: Development
Content root path: E:\Projects-STS\TSIC\TSIC.STS
Now listening on: https://localhost:44340
Application started. Press Ctrl+C to shut down.
[09:28:13 Debug] IdentityServer4.Startup
Login Url: /Account/Login

[09:28:13 Debug] IdentityServer4.Startup
Login Return Url Parameter: ReturnUrl

[09:28:13 Debug] IdentityServer4.Startup
Logout Url: /Account/Logout

[09:28:13 Debug] IdentityServer4.Startup
ConsentUrl Url: /consent

[09:28:13 Debug] IdentityServer4.Startup
Consent Return Url Parameter: returnUrl

[09:28:13 Debug] IdentityServer4.Startup
Error Url: /home/error

[09:28:13 Debug] IdentityServer4.Startup
Error Id Parameter: errorId

[09:28:25 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/authorize matched to endpoint type Authorize

[09:28:25 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Authorize, successfully created handler: IdentityServer4.Endpoints.AuthorizeEndpoint

[09:28:25 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize

[09:28:25 Debug] IdentityServer4.Endpoints.AuthorizeEndpoint
Start authorize request

[09:28:25 Debug] IdentityServer4.Endpoints.AuthorizeEndpoint
No user present in authorize request

[09:28:25 Debug] IdentityServer4.Validation.AuthorizeRequestValidator
Start authorize request protocol validation

[09:28:26 Debug] IdentityServer4.EntityFramework.Stores.ClientStore
angularclient found in database: True

[09:28:26 Debug] IdentityServer4.Stores.ValidatingClientStore
client configuration validation for client angularclient succeeded.

[09:28:27 Debug] IdentityServer4.EntityFramework.Stores.ResourceStore
Found ["openid", "profile", "email", "api1scope"] identity scopes in database

[09:28:27 Debug] IdentityServer4.EntityFramework.Stores.ResourceStore
Found [] API scopes in database

[09:28:27 Debug] IdentityServer4.EntityFramework.Stores.ResourceStore
Found ["openid", "profile", "email", "api1scope"] identity scopes in database

[09:28:27 Debug] IdentityServer4.EntityFramework.Stores.ResourceStore
Found [] API scopes in database
我想我离这里很近,只需要往正确的方向推一下

我目前的重点是:

[09:28:27 Debug] IdentityServer4.EntityFramework.Stores.ResourceStore
Found [] API scopes in database
认为这与api项目错误有关:

IDX10214: Audience validation failed. Audiences: 'https://localhost:44340/resources'. Did not match: validationParameters.ValidAudience: 'api1' or validationParameters.ValidAudiences: 'null'.
这让我感到很不舒服,因为数据库的dbo.ApiScopes中确实有一个来自SQL Server的条目:

Id  Name    DisplayName Description Required    Emphasize   ShowInDiscoveryDocument ApiResourceId
9   api1    Scope for the api1 ApiResource  NULL    0   0   1   12

我非常感谢您对我的帮助

 openIDImplicitFlowConfiguration.scope = 'openid profile email api1scope';
但是,它应该与api1的一个有效作用域相匹配:

 openIDImplicitFlowConfiguration.scope = 'openid profile email api1';

如果您请求至少一个属于给定Api的作用域,并且您的客户端允许该作用域,则Identity Server 4仅将Api资源添加为有效的令牌访问群体。

在您的配置中,您有:

 openIDImplicitFlowConfiguration.scope = 'openid profile email api1scope';
但是,它应该与api1的一个有效作用域相匹配:

 openIDImplicitFlowConfiguration.scope = 'openid profile email api1';

如果您请求至少一个属于给定Api的作用域,并且您的客户端允许该作用域,则Identity Server 4仅将Api资源添加为有效的令牌访问群体。

Vidmantas,感谢您的响应,我尝试了该操作,但出现了相同的错误。然后我幸运地解决了(是id令牌和访问令牌中指示的不同受众的问题,以及Api项目startup.cs中的IdentityServer4配置如何设置id令牌aud:)的问题:

明白了,希望这能帮助其他人:

返回登录后的angular客户端:

id令牌:

{
  "nbf": 1550240640,
  "exp": 1550273640,
  "iss": "https://localhost:44340",
  "aud": "https://localhost:44340/resources",
  "client_id": "angularclient",
  "sub": "71765055-647D-432E-AFB6-0F84218D0247",
  "auth_time": 1550240638,
  "idp": "local",
  "regid": "xxxx",
  "jseg": "xxxxx",
  "jobid": "b0984a87-172a-436e-a382-e95de3e1059f",
  "role": "xxxx",
  "given_name": "xxxxx",
  "family_name": "xxxx",
  "email": "xxxx",
  "scope": [
    "openid",
    "profile",
    "email"
  ],
  "amr": [
    "pwd"
  ]
}
和访问令牌:

{
  "nbf": 1550240640,
  "exp": 1550243640,
  "iss": "https://localhost:44340",
  "aud": "angularclient",
  "nonce": "N0.55036966062308791550240634889",
  "iat": 1550240640,
  "at_hash": "yNVxDVHkmEmUvurl7XlzuA",
  "sid": "f54dee03793e7cc202b57f1d6de7622e",
  "sub": "71765055-647D-432E-AFB6-0F84218D0247",
  "auth_time": 1550240638,
  "idp": "local",
  "preferred_username": "TSICSuperUser",
  "name": "xxxx",
  "email": "xxxxx",
  "email_verified": true,
  "regid": "xxxxx",
  "jseg": "xxxxx",
  "jobid": "xxxxxxf",
  "role": "xxxxx",
  "given_name": "xxxx",
  "family_name": "xxxxx",
  "amr": [
    "pwd"
  ]
}
注意不同的受众(澳元:)

Asp.Net Core 2.2 Api项目startup.cs配置了IdentityServer 4:

                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("IdentityServer4Strings:Authority");
                    options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer4Strings:RequireHttpsMetadata");
                    options.ApiName = "api1";

                    options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
                    options.ApiSecret = Configuration.GetValue<string>("IdentityServer4Strings:STSTSICApisSecuredSecret");
                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
                });
错误的“api1”:
不匹配:validationParameters.ValidAudience:'api1'

参考startup.cs

.AddIdentityServerAuthentication
options.ApiName = "api1";
将Asp.Net Core 2.2 Api项目startup.cs更改为:

            // critical for bearer authentication, the audience of the id token (set by Options.ApiName) is equal to this value
            var idTokenAudience = $"{Configuration.GetValue<string>("IdentityServer4Strings:Authority")}/resources";

            services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("IdentityServer4Strings:Authority");
                    options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer4Strings:RequireHttpsMetadata");
                    options.ApiName = idTokenAudience;

                    options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
                    options.ApiSecret = Configuration.GetValue<string>("IdentityServer4Strings:STSTSICApisSecuredSecret");
                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
                });

维德曼塔斯,谢谢你的回复,我试过了,同样的错误也发生了。然后我幸运地解决了(是id令牌和访问令牌中指示的不同受众的问题,以及Api项目startup.cs中的IdentityServer4配置如何设置id令牌aud:)的问题:

明白了,希望这能帮助其他人:

返回登录后的angular客户端:

id令牌:

{
  "nbf": 1550240640,
  "exp": 1550273640,
  "iss": "https://localhost:44340",
  "aud": "https://localhost:44340/resources",
  "client_id": "angularclient",
  "sub": "71765055-647D-432E-AFB6-0F84218D0247",
  "auth_time": 1550240638,
  "idp": "local",
  "regid": "xxxx",
  "jseg": "xxxxx",
  "jobid": "b0984a87-172a-436e-a382-e95de3e1059f",
  "role": "xxxx",
  "given_name": "xxxxx",
  "family_name": "xxxx",
  "email": "xxxx",
  "scope": [
    "openid",
    "profile",
    "email"
  ],
  "amr": [
    "pwd"
  ]
}
和访问令牌:

{
  "nbf": 1550240640,
  "exp": 1550243640,
  "iss": "https://localhost:44340",
  "aud": "angularclient",
  "nonce": "N0.55036966062308791550240634889",
  "iat": 1550240640,
  "at_hash": "yNVxDVHkmEmUvurl7XlzuA",
  "sid": "f54dee03793e7cc202b57f1d6de7622e",
  "sub": "71765055-647D-432E-AFB6-0F84218D0247",
  "auth_time": 1550240638,
  "idp": "local",
  "preferred_username": "TSICSuperUser",
  "name": "xxxx",
  "email": "xxxxx",
  "email_verified": true,
  "regid": "xxxxx",
  "jseg": "xxxxx",
  "jobid": "xxxxxxf",
  "role": "xxxxx",
  "given_name": "xxxx",
  "family_name": "xxxxx",
  "amr": [
    "pwd"
  ]
}
注意不同的受众(澳元:)

Asp.Net Core 2.2 Api项目startup.cs配置了IdentityServer 4:

                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("IdentityServer4Strings:Authority");
                    options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer4Strings:RequireHttpsMetadata");
                    options.ApiName = "api1";

                    options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
                    options.ApiSecret = Configuration.GetValue<string>("IdentityServer4Strings:STSTSICApisSecuredSecret");
                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
                });
错误的“api1”:
不匹配:validationParameters.ValidAudience:'api1'

参考startup.cs

.AddIdentityServerAuthentication
options.ApiName = "api1";
将Asp.Net Core 2.2 Api项目startup.cs更改为:

            // critical for bearer authentication, the audience of the id token (set by Options.ApiName) is equal to this value
            var idTokenAudience = $"{Configuration.GetValue<string>("IdentityServer4Strings:Authority")}/resources";

            services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("IdentityServer4Strings:Authority");
                    options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer4Strings:RequireHttpsMetadata");
                    options.ApiName = idTokenAudience;

                    options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
                    options.ApiSecret = Configuration.GetValue<string>("IdentityServer4Strings:STSTSICApisSecuredSecret");
                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
                });

我第一眼看到两件事。首先-api作用域不是标识资源。把它从那里拿走。在Angular module.ts中,第二个将作用域固定为api1,而不是api1scope。然后-确保
IdentityServer4Strings:Authority
和传递给angular应用程序的值(
environment.oidc.stsServer
)相同。该机构
https://localhost:44340/resources
出现错误似乎很奇怪。我第一眼看到两件事。首先-api作用域不是标识资源。把它从那里拿走。在Angular module.ts中,第二个将作用域固定为api1,而不是api1scope。然后-确保
IdentityServer4Strings:Authority
和传递给angular应用程序的值(
environment.oidc.stsServer
)相同。该机构
https://localhost:44340/resources
您遇到的错误似乎很奇怪。