You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenIddict部署IIS后GetOpenIddictServerRequest为空及重定向异常

部署至IIS后OpenIddict核心功能异常排查

问题现象

  • GetOpenIddictServerRequest()在IIS环境下始终返回null,本地测试正常;
  • 移除上述代码后调用SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)时抛出异常:System.InvalidOperationException: A sign-in response cannot be returned from this endpoint.

环境配置

  • .NET 8.0
  • OpenIddict 4.10.1

OpenIddict配置代码

public static void AddConsioAuthenticationDict(this IServiceCollection services, IConfiguration configuration)
{
    var connectionString = configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

    // Enable Quartz.NET integration.
    services.AddQuartz(options =>
    {
        options.UseMicrosoftDependencyInjectionJobFactory();
        options.UseSimpleTypeLoader();
        options.UseInMemoryStore();
    });

    X509Certificate2? privateKey = null;
    string currentDirectory = Directory.GetCurrentDirectory();
    var bytes = File.ReadAllBytes(currentDirectory + "\\secretCert.pfx");
    if (bytes != null && bytes.Length > 2)
    {
        privateKey = new X509Certificate2(bytes, "somepassword");
    }

    if (privateKey == null)
    {
        X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

        for (int i = 0; i < store.Certificates.Count; i++)
        {
            X509Certificate2? cert = store.Certificates[i];
            var serial = configuration["NetsSoapSetting:certificate-serial"];
            var thumbprint = configuration["NetsSoapSetting:certificate-thumbprint"];

            if (string.Equals(cert.SerialNumber, serial, StringComparison.OrdinalIgnoreCase) ||
                string.Equals(cert.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase))
            {
                privateKey = cert;
                break;
            }
        }
    }

    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseSqlServer(connectionString);
        // Register the entity sets needed by OpenIddict.
        // Note: use the generic overload if you need to replace the default OpenIddict entities.
        options.UseOpenIddict();
    });
    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddOpenIddict()
        .AddCore(option =>
        {
            option.UseEntityFrameworkCore()
                .UseDbContext<ApplicationDbContext>();
            option.UseQuartz();
        })
        .AddServer(option =>
        {
            option.RegisterScopes(
                OpenIddictConstants.Scopes.Profile,
                OpenIddictConstants.Scopes.OpenId,
                OpenIddictConstants.Permissions.Scopes.Profile);
            option.RequireProofKeyForCodeExchange();

            option.Configure(options => options.CodeChallengeMethods.Add(OidcConstants.CodeChallengeMethods.Plain));
            option.SetAuthorizationEndpointUris("/connect/authorize/");
            // Enable the token endpoint.8
            option.SetTokenEndpointUris("/connect/token/");

            // Enable the client credentials flow.
            option.SetUserinfoEndpointUris("/connect/userinfo/");
            option.SetLogoutEndpointUris("/connect/logout/");

            option.AllowImplicitFlow();
            option.AllowClientCredentialsFlow();
            option.AllowPasswordFlow();
            option.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange(); ;
            option.AllowHybridFlow();
            option.AllowRefreshTokenFlow();

            option
                .AddSigningCertificate(privateKey);
            //option.AddDevelopmentEncryptionCertificate()
            //    .AddDevelopmentSigningCertificate();

            // Register the ASP.NET Core host and configure the ASP.NET Core options.
            option.UseAspNetCore()
                .EnableTokenEndpointPassthrough()
                .EnableLogoutEndpointPassthrough()
                .EnableAuthorizationEndpointPassthrough()
                .EnableUserinfoEndpointPassthrough();
            option
                .AddEphemeralEncryptionKey()
                .AddEphemeralSigningKey();
            // encrypt the access token to hide all info public!
            //.DisableAccessTokenEncryption();
        })
        .AddValidation(options =>
        {
            // Import the configuration from the local OpenIddict server instance.
            options.UseLocalServer();
            options.EnableTokenEntryValidation();
            // Register the ASP.NET Core host.
            options.UseAspNetCore();
        });
}

Authorize方法代码

[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
    //var request = HttpContext.GetOpenIddictServerRequest() ??
    //              throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
    var remoteIpAddress = HttpContext.Connection.RemoteIpAddress;
    //var GrantTypes = request.GrantType;
    //var clientSecret = request.ClientSecret;
    // Retrieve the user principal stored in the authentication cookie.
    //var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);

    // If the user principal can't be extracted, redirect the user to the login page.
    if (!result.Succeeded)
    {
        return Challenge(
            authenticationSchemes: IdentityConstants.ApplicationScheme,
            properties: new AuthenticationProperties
            {
                RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
                    Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
            });
    }

    var userId = string.Empty;

    if (HttpContext.User.Identity != null && !String.IsNullOrEmpty(HttpContext.User.Identity.Name))
    {
        var username = HttpContext.User.Identity.Name;
        var user = await _userManager.FindByNameAsync(username);

        if (user != null)
        {
            userId = user.Id;
        }
    }

    var claims = new List<Claim>
    {
        // 'subject' claim which is required
        new Claim(OpenIddictConstants.Claims.Subject, userId),
        new Claim(OpenIddictConstants.Claims.ExpiresAt, DateTime.UtcNow.AddHours(1).ToString()),
        new Claim("someclaim", "somevalue").SetDestinations(OpenIddictConstants.Destinations.AccessToken),
        new Claim("originalIp", remoteIpAddress.ToString()).SetDestinations(OpenIddictConstants.Destinations.AccessToken),
    };

    var claimsIdentity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    // Set requested scopes (this is not done automatically)
    //claimsPrincipal.SetScopes(request.GetScopes());

    // Signing in with the OpenIddict authentication scheme trigger OpenIddict to issue a code (which can be exchanged for an access token)
    // Create a new authentication ticket for the user's principal
    return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

问题原因及修复方案

1. GetOpenIddictServerRequest()返回null的原因及修复

核心原因是IIS的路径处理与OpenIddict的端点匹配逻辑不兼容:

  • 配置的授权端点带末尾斜杠/connect/authorize/,但实际请求可能不带斜杠,或者IIS的URL重写规则修改了路径,导致OpenIddict无法识别该请求属于授权端点;
  • IIS经典托管管道模式会导致ASP.NET Core中间件无法正常解析请求上下文;
  • Directory.GetCurrentDirectory()在IIS环境下指向系统目录,而非应用程序根目录,可能导致证书加载失败,间接影响OpenIddict初始化。

修复步骤:

  • 统一端点路径:将SetAuthorizationEndpointUris("/connect/authorize/")改为SetAuthorizationEndpointUris("/connect/authorize"),去掉末尾斜杠,确保与控制器路由一致;
  • 检查IIS托管模式:将应用程序池的托管管道模式设置为集成;
  • 修正证书加载路径:使用IWebHostEnvironment.ContentRootPath替代Directory.GetCurrentDirectory()获取应用程序根目录,避免路径错误;
  • 排查URL重写规则:确保IIS没有修改/connect/*前缀的请求路径。

2. SignIn(...)抛出异常的原因及修复

该异常是第一个问题的连锁反应:当OpenIddict无法识别当前请求为授权端点请求时,不会为响应生成必要的上下文,此时调用SignIn生成授权响应自然会失败。

修复步骤:

  • 先解决第一个问题,确保GetOpenIddictServerRequest()能正常获取请求对象;
  • 恢复claimsPrincipal.SetScopes(request.GetScopes());代码,OpenIddict需要根据请求的Scope生成合法的令牌;
  • 确保认证票据关联请求的客户端信息,比如从request.ClientId添加对应Claim,保证响应能正确关联到发起请求的客户端。

内容的提问来源于stack exchange,提问作者Hai Pham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 11:27:48