簡體   English   中英

IdentityServer4 使用帶有 API 的客戶端憑據工作流(或嘗試模擬 OIDC 調用)

[英]IdentityServer4 Using Client Credential Workflow with an API (Or trying to emulate OIDC calls)

我想使用 IdentityServer4 來保護使用 Windows 憑據的 API。 我在 web 應用程序中創建了一個工作示例,但嘗試模仿 OIDC 調用證明很麻煩。 文件中,似乎表明使用 API 的唯一方法是使用 ClientID 和密鑰進行身份驗證。 我想看看這是不是真的。 下面我將添加我目前正在嘗試模擬 OIDC 工作流程的網絡調用。 希望有更好的方法來解決這個問題或一組更簡單的調用。 無論哪種方式,我都很感激幫助。

最小的工作示例(所有通過郵遞員撥打的電話)

  1. 我調用登錄端點“[GET] https://localhost:44353/Account/Login”,這將返回一個 200 OK 登錄頁面 HTML,更重要的是我的“.AspNetCore.Antiforgery”cookie

  2. 我使用 NTLM 身份驗證調用我的挑戰端點“[GET] https://localhost:44353/External/Challenge?provider=Windows”並提供我的 windows 憑據。 這將返回 401 Unauthorized 和 cookie “idsrv.external”,我認為 401 只是由於重定向,我實際上只需要 cookie。

  3. 我調用回調端點“[GET] https://localhost:44353/External/Callback”並刪除我的“idsrv.external”cookie 並將 cookies 稱為“idsrv.session”和“idsrv”。

  4. 我現在嘗試使用我目前收到的 cookies 調用我的 API 端點“[GET] https://localhost:16385/managementservice/schema”。 這將返回給我 OIDC 權限請求頁面。

  5. 我從最后一個請求的 html 中獲取返回 URL 和令牌,並使用以下表格調用“[POST] https://localhost:44353/Consent” 這將返回 200 OK html 並帶有一個調用“https://localhost:16385/signin-oidc”的按鈕。

圖片

  1. 我使用上一個 html 中的數據然后調用“[POST] https://localhost:16385/signin-oidc”,如下所示。 我有 5 個 cookies 組; .AspNetCore.OpenIdConnect.Nonce、.AspNetCore.Correlation.oidc、.AspNetCore.Antiforgery、idsrv.session 和 idsrv。 但是,此調用返回 500 Internal Server Error 而不是像 UI 一樣的 302 Found。 關於這里出了什么問題的任何想法?

圖片

我可以根據需要提供更多數據或特定文件。 這只是一個起點。

編輯:我收到了提供適用文件的請求。 我的客戶端應用程序是 ASP.NET 核心 API,我正在使用 postman。

身份服務器啟動.cs

using IdentityModel;
using IdentityServer4;
using IdentityServer4.Quickstart.UI;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

namespace IdentityServerTemplate
{
    public class Startup
    {
        public IWebHostEnvironment Environment { get; }
        public IConfiguration Configuration { get; }

        public Startup(IWebHostEnvironment environment, IConfiguration configuration)
        {
            Environment = environment;
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddHttpClient();

            // configures IIS out-of-proc settings (see https://github.com/aspnet/AspNetCore/issues/14882)
            services.Configure<IISOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = true;
            });

            // configures IIS in-proc settings
            services.Configure<IISServerOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = true;
            });

            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            });
            //.AddTestUsers(TestUsers.Users);

            // in-memory, code config
            builder.AddInMemoryIdentityResources(Config.Ids);
            builder.AddInMemoryApiResources(Config.Apis);
            builder.AddInMemoryClients(Config.Clients);

            services.AddScoped<IProfileService, ADProfileService>();

            // or in-memory, json config
            //builder.AddInMemoryIdentityResources(Configuration.GetSection("IdentityResources"));
            //builder.AddInMemoryApiResources(Configuration.GetSection("ApiResources"));
            //builder.AddInMemoryClients(Configuration.GetSection("clients"));

            // not recommended for production - you need to store your key material somewhere secure
            builder.AddDeveloperSigningCredential();

            services.AddAuthentication();
                //.AddGoogle(options =>
                //{
                //    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

                //    // register your IdentityServer with Google at https://console.developers.google.com
                //    // enable the Google+ API
                //    // set the redirect URI to http://localhost:5000/signin-google
                //    options.ClientId = "copy client ID from Google here";
                //    options.ClientSecret = "copy client secret from Google here";
                //});
        }

        public void Configure(IApplicationBuilder app)
        {
            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();

            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthorization();
            app.UseAuthentication();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
    }
}

身份服務器配置.cs

using IdentityModel;
using IdentityServer4.Models;
using System.Collections.Generic;

namespace IdentityServerTemplate
{
    public static class Config
    {
        public static IEnumerable<IdentityResource> Ids =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResources.Address(),
            };


        public static IEnumerable<ApiResource> Apis =>
            new ApiResource[]
            {
                // new ApiResource("api1", "My API #1")

                new ApiResource("api1", "My API", new[] { JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Address, "upn_custom"})
            };


        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                // client credentials flow client
                new Client
                {
                    ClientId = "identity.server",
                    ClientName = "Identity Server Client",

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MVC client using code flow + pkce
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "mvc.code",
                    ClientName = "MVC Client",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                    //RequirePkce = true,
                    RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "https://localhost:5003/signin-oidc" },
                    RedirectUris = { "https://localhost:5003/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:5003/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MCW Appserver
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "mcw.appserver",
                    ClientName = "MCW AppServer",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                    RequirePkce = false,
                    //RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "http://localhost:16835/signin-oidc" },
                    RedirectUris = { "https://localhost:16385/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:16835/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:16835/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MVC client using code flow + pkce
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "ptp.appserv",
                    ClientName = "PTP AppServ",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                    //RequirePkce = true,
                    RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "https://localhost:30001/signin-oidc" },
                    RedirectUris = { "https://localhost:30001/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:30001/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:30001/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },             

                // SPA client using code flow + pkce
                new Client
                {
                    ClientId = "spa",
                    ClientName = "SPA Client",
                    ClientUri = "http://identityserver.io",

                    AllowedGrantTypes = GrantTypes.Code,
                    RequirePkce = true,
                    RequireClientSecret = false,

                    RedirectUris =
                    {
                        "http://localhost:5002/index.html",
                        "http://localhost:5002/callback.html",
                        "http://localhost:5002/silent.html",
                        "http://localhost:5002/popup.html",
                    },

                    PostLogoutRedirectUris = { "http://localhost:5002/index.html" },
                    AllowedCorsOrigins = { "http://localhost:5002" },

                    AllowedScopes = { "openid", "profile", "api1" }
                }
            };
    }
}

ASP.NET API 服務啟動.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Tps.ManagedClaimsWell.ApplicationServer.AppServInternals;
using Tps.ManagedClaimsWell.ApplicationServer.DataAccess;
using Tps.ManagedClaimsWell.ApplicationServer.Utility;

namespace ManagedClaimsWell.ApplicationServer.Core
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

            services.AddControllers()
                .AddNewtonsoftJson();

            services.AddHttpClient();

            var appServSettings = new AppServSettings(Configuration);

            ClaimsWellCache.Inst.Load(ClaimsWellSchemaData.Load, IdentityData.UpdateNameLastAccessed);

            services.AddSingleton<IDiscoveryCache>(r =>
            {
                var factory = r.GetRequiredService<IHttpClientFactory>();
                return new DiscoveryCache(Constants.Authority, () => factory.CreateClient());
            });

            //services.AddAuthentication(options =>
            //{
            //    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //    options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
            //})
            //.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            //{
            //    options.
            //    options.ExpireTimeSpan = TimeSpan.FromDays(1);
            //});

            services.AddAuthorization(options =>
            {
                options.AddPolicy("scope", policy =>
                {
                    policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme)
                        .RequireAuthenticatedUser()
                        .RequireClaim("scope", "api1");
                });
            });

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie(options =>
            {
                options.Cookie.Name = "idsrv";
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = Constants.Authority;
                options.RequireHttpsMetadata = false;

                options.ClientId = "mcw.appserver";
                options.ClientSecret = "secret";

                // code flow + PKCE (PKCE is turned on by default)
                options.ResponseType = "code";
                options.UsePkce = true;

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("email");
                options.Scope.Add("api1");
                ////options.Scope.Add("transaction:123");
                ////options.Scope.Add("transaction");
                options.Scope.Add("offline_access");

                // not mapped by default
                options.ClaimActions.MapJsonKey(JwtClaimTypes.WebSite, "website");

                // keeps id_token smaller
                options.GetClaimsFromUserInfoEndpoint = true;
                options.SaveTokens = true;

                var handler = new JwtSecurityTokenHandler();
                handler.InboundClaimTypeMap.Clear();
                options.SecurityTokenValidator = handler;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = JwtClaimTypes.Name,
                    RoleClaimType = JwtClaimTypes.Role,
                };
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers()
                    .RequireAuthorization();
            });
        }
    }
}

不知道是否是問題,但是,一個問題是您在 IdentityServer 中

        app.UseIdentityServer();
        app.UseAuthorization();
        app.UseAuthentication();

請參閱這篇文章,了解如何配置管道。

特別是,請注意它說:

UseIdentityServer 包括對 UseAuthentication 的調用,因此不必同時擁有這兩者。

正如我在評論中所說,嘗試從 postman 向 /signin-oidc 發送請求可能會由於身份驗證工作方式中的各種內置功能而失敗。 一個問題是您沒有 OpenIdConnect 處理程序期望的正確 state 參數。 它是一個隨機值,每次用戶嘗試進行身份驗證時都會更改。

您的“ASP.NET API Service Startup.cs”是“客戶端”,而不是 API。 您擁有的內容是供最終用戶登錄的。 在這里使用 postman 沒有任何意義。 API 可能應該使用 UseJwtBearer 處理程序,並且您可以使用 PostMan 和有效的訪問令牌向該處理程序發送請求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM