IdentityServer4 资源所有者密码和 Win 身份验证:未授权

IdentityServer4 Resource owner password and Win auth: unauthorized

在过去的几天里,我一直在阅读 IdentityServer4 文档并使用资源所有者密码将我的示例服务器 + 示例客户端放在一起。现在我想并行添加 Windows 身份验证(将通过 Active Directory 完成),因此客户端应用程序(不是网络应用程序而是桌面应用程序)可以提示用户输入凭据或使用 Windows 通过 Active Directory 进行身份验证。

关于 Windows 身份验证的文档解释了如何配置 IIS 或 HTTP.Sys,但我想要的是:

  1. 用户打开应用程序
  2. 应用程序使用单点登录 post 网络请求 api 请求令牌和刷新令牌
  3. 网络 api 使用 windows 身份验证来验证用户身份和 returns 令牌

我已尝试遵循此 answer,但它不起作用(它 returns 未经授权)。

网络api:Startup.cs

    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvcCore()
                .AddAuthorization()
                .AddJsonFormatters();


            services.AddAuthentication("Bearer")    
            .AddJwtBearer(options =>
            {
                // base-address of your identityserver
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                // name of the API resource
                options.Audience = "api/user";
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.AddIdentityServer(options => { options.PublicOrigin = "http://localhost:5000"; options.MutualTls.Enabled = false; })                
                .AddExtensionGrantValidator<WinAuthGrantValidator>()
                .AddDeveloperSigningCredential()
                .AddTestUsers(Config.GetUsers())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddInMemoryIdentityResources(Config.GetIdentityResources());

        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env,
            ILogger<Startup> logger, IServer server)
        {
            app.Use(async (context, next) =>
            {
                context.Features.Get<IHttpMaxRequestBodySizeFeature>()
                    .MaxRequestBodySize = 10 * 1024;

                var serverAddressesFeature =
                    app.ServerFeatures.Get<IServerAddressesFeature>();
                var addresses = string.Join(", ", serverAddressesFeature?.Addresses);

                logger.LogInformation($"Addresses: {addresses}");

                await next.Invoke();

            });
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");

                app.UseHsts();
            }

            // Enable HTTPS Redirection Middleware when hosting the app securely.
            //app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseMvc();
            app.UseIdentityServer();
            app.UseAuthentication();
        }
    }

    internal static class Config
    {
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "alice",
                    Password = "password"
                },
                new TestUser
                {
                    SubjectId = "2",
                    Username = "bob",
                    Password = "password"
                }
            };
        }

        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                // other clients omitted...

                // resource owner password grant client
                new Client
                {
                    ClientId = "ro.client",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                   // AllowedScopes = { "api1" }
                   AllowedScopes = { "api/user" }
                },
            new Client
            {
                ClientId = "winauth",
                AllowedGrantTypes =  new List<string>{ "windows_auth" },

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
               // AllowedScopes = { "api1" }
               AllowedScopes = { "api/user" }
             }
            };
        }

        internal static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource { Name = "api1",Scopes = new List<Scope> { new Scope {  Name = "api1",
                    DisplayName = "Full access to API 2"}  }, Enabled = true,  ApiSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256())
                    }
                },
                new ApiResource { Name = "api/user",Scopes = new List<Scope> { new Scope {  Name = "api/user",
                    DisplayName = "Full access to API 2"}  }, Enabled = true,  ApiSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256())
                    }
                }};
        }


        public static List<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }
    }



public class WinAuthGrantValidator : IExtensionGrantValidator
    {
    private readonly HttpContext httpContext;

    public string GrantType => "windows_auth";

    public WinAuthGrantValidator(IHttpContextAccessor httpContextAccessor)
    {
        httpContext = httpContextAccessor.HttpContext;
    }

    public async Task ValidateAsync(ExtensionGrantValidationContext context)
    {
        // see if windows auth has already been requested and succeeded
        var result = await httpContext.AuthenticateAsync("Windows");
        if (result?.Principal is WindowsPrincipal wp)
        {
            context.Result = new GrantValidationResult(wp.Identity.Name, GrantType, wp.Claims);
        }
        else
        {
            // trigger windows auth
            await httpContext.ChallengeAsync("Windows");
            context.Result = new GrantValidationResult { IsError = false, Error = null, Subject = null };
        }
    }
}
}

网络API:Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var isService = !(Debugger.IsAttached || args.Contains("--console"));

        if (isService)
        {
            var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
            var pathToContentRoot = Path.GetDirectoryName(pathToExe);
            Directory.SetCurrentDirectory(pathToContentRoot);
        }

        var builder = CreateWebHostBuilder(
            args.Where(arg => arg != "--console").ToArray());

        var host = builder.Build();

        if (isService)
        {
            // To run the app without the CustomWebHostService change the
            // next line to host.RunAsService();
            host.RunAsCustomService();
        }
        else
        {
            host.Run();
        }
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddEventLog();
            })
            .ConfigureAppConfiguration((context, config) =>
            {
                // Configure the app here.
            })
            .UseStartup<Startup>()
            .UseHttpSys(options =>
            {
                options.AllowSynchronousIO = true;
                options.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.Kerberos | Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.NTLM;
                options.Authentication.AllowAnonymous = true;
                options.MaxConnections = null;
                options.MaxRequestBodySize = 30000000;
                options.UrlPrefixes.Add("http://localhost:5000");
            });
}

网络APIUserController.cs

[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
[ApiController]
public class UserController : ControllerBase
{
    // GET api/user
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { User.Identity.Name, User.Identity.AuthenticationType };
    }
}

客户代码:

using (var client = new HttpClient())
{
    disco = await client.GetDiscoveryDocumentAsync(new   DiscoveryDocumentRequest
    {
        Address = baseUrl,
        Policy = { RequireHttps = false }
    });
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        Console.ReadLine();
        return;
     }
     var httpHandler = new HttpClientHandler
     {
         UseDefaultCredentials = true,
     };

     using (var client = new HttpClient())
     {
         // request token
         TokenResponse tokenResponse = await client.RequestTokenAsync(new TokenRequest
         {
             GrantType = "windows_auth",
             Address = disco.TokenEndpoint,
             ClientId = "winauth",
             ClientSecret = "secret"
         });

         if (tokenResponse.IsError)
         {
             Console.WriteLine(tokenResponse.Error);
             Console.ReadLine();
            return;
         }
     }

它returns未经授权。

我找到了一个解决方案:我需要用 SubjectId = MYDOMAIN\myusername 配置一个 TestUser 然后它起作用了。

出现的错误过于混乱。