登录页面重定向缺少端口 - nginx 生产服务器上的 OpenIddict/MSIdentity & Blazor



我正在开发一个Blazor项目(asp.net核心托管(,该项目发布在Linux主机上,Nginx作为web服务器和代理,通过特定端口将调用重定向到我的域,并将其重定向到应用程序。(https://example:9999=>localhost:10000(

我集成了OpenIddict(OAuth(进行授权,因为IdentityServer在最新的.net版本上需要自定义许可证。

我目前面临的问题只发生在生产中,当OpenIddict重定向到具有自定义路由参数的登录页面时,URL缺少我在生产中使用的自定义端口。在localhost(Dev/本地机器(上,提供了正确的端口https://localhost:7115,但在生产中,我被重定向到https://example.com/Identity/Account/Login,而不是https://example.com:9999/Identity/Account/Login

当我手动更改URL时,一切都很好,我可以正确登录到我的应用程序。

OpenIddict服务器设置:

builder.Services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIddictConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIddictConstants.Claims.Role;
});
builder.Services.AddQuartz(options =>
{
options.UseMicrosoftDependencyInjectionJobFactory();
options.UseSimpleTypeLoader();
options.UseInMemoryStore();
});
builder.Services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
builder.Services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
options.UseQuartz();
})
.AddServer(options =>
{
options.SetIssuer(new Uri(publicHostFullUrl));
options.SetAuthorizationEndpointUris(authorizationEndpoint)
.SetLogoutEndpointUris(logoutEndpoint)
.SetTokenEndpointUris(tokenEndpoint)
.SetUserinfoEndpointUris(userInfoEndpoint);
options.RegisterScopes(OpenIddictConstants.Scopes.Email, OpenIddictConstants.Permissions.Scopes.Profile, OpenIddictConstants.Permissions.Scopes.Roles);
options.AllowAuthorizationCodeFlow()
.AllowRefreshTokenFlow();
options.AddEncryptionCertificate(certificate)
.AddSigningCertificate(certificate);
}
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableStatusCodePagesIntegration()
.EnableTokenEndpointPassthrough();
options.AcceptAnonymousClients();
options.DisableScopeValidation();
})
.AddValidation(options =>
{
options.SetIssuer(new Uri(publicHostFullUrl));
options.UseLocalServer();
options.UseAspNetCore();
});

客户端设置:

builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.ClientId = clientId;
options.ProviderOptions.Authority = $"{builder.HostEnvironment.BaseAddress}";
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.ResponseMode = "query";
options.AuthenticationPaths.RemoteRegisterPath = $"{builder.HostEnvironment.BaseAddress}Identity/Account/Register";
options.AuthenticationPaths.LogInCallbackPath = $"{builder.HostEnvironment.BaseAddress}/Identity/Account/Login";
options.AuthenticationPaths.LogInPath = $"{builder.HostEnvironment.BaseAddress}/Identity/Account/Login";
});

我的授权控制器操作,将ChallengeResult返回到登录页面:

[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
if (request.HasPrompt(Prompts.Login))
{
var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login));
var parameters = Request.HasFormContentType ? Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList();
parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt)));
return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters)
});
}
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (!result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
{
if (request.HasPrompt(Prompts.None))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}!));
}

return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
});
}
var user = await _userManager.GetUserAsync(result.Principal) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) ??
throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
status: Statuses.Valid,
type: AuthorizationTypes.Permanent,
scopes: request.GetScopes()).ToListAsync();
switch (await _applicationManager.GetConsentTypeAsync(application))
{
case ConsentTypes.External when !authorizations.Any():
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application."
}!));
case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Any():
case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent):
var principal = await _signInManager.CreateUserPrincipalAsync(user);
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
var authorization = authorizations.LastOrDefault();
if (authorization == null)
{
authorization = await _authorizationManager.CreateAsync(
principal: principal,
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
type: AuthorizationTypes.Permanent,
scopes: principal.GetScopes());
}
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim, principal));
}
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
case ConsentTypes.Explicit when request.HasPrompt(Prompts.None):
case ConsentTypes.Systematic when request.HasPrompt(Prompts.None):
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"Interactive user consent is required."
}!));
default:
return View(new AuthorizeViewModel
{
ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
Scope = request.Scope
});
}
}
[Authorize, FormValueRequired("submit.Accept")]
[HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]
public async Task<IActionResult> Accept()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
var user = await _userManager.GetUserAsync(User) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) ??
throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
status: Statuses.Valid,
type: AuthorizationTypes.Permanent,
scopes: request.GetScopes()).ToListAsync();
if (!authorizations.Any() && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application."
}!));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
var authorization = authorizations.LastOrDefault();
if (authorization == null)
{
authorization = await _authorizationManager.CreateAsync(
principal: principal,
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
type: AuthorizationTypes.Permanent,
scopes: principal.GetScopes());
}
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim, principal));
}
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

更新

我的nginx配置可以更好地理解托管环境:

server {
listen 9999 ssl;
server_name example.com;
ssl_certificate /etc/../certificate.crt;
ssl_certificate_key /etc/../key.key;
location / {
proxy_pass              https://example.com:10000;
proxy_http_version      1.1;
proxy_set_header        Upgrade $http_upgrade;
proxy_set_header        Connection keep-alive;
proxy_set_header        Host $host;
proxy_cache_bypass      $http_upgrade;
proxy_set_header        X-Forwared-For $proxy_add_x_forwarded_for;
proxy_set_header        X-Forwarded-Proto $scheme;
}
}

更新2

我仍然面临这个问题。经过更多的测试&经过猜测,我发现通过将客户端程序.cs中的options.AcuthenticationPaths.RemoteRegisterPath设置为绝对URL,重定向到注册页面是正确的。没有任何属性可以将登录路径设置为其绝对URL,只有LogInPath,但没有RemoteLogInPath,就像它用于注册页面一样。

对于我遗漏或做错的地方,我们将不胜感激。

您的问题源于在构建重定向URL时使用Request.PathBase。PathBase通常是一个空字符串。相反,您可以使用Request.Host来获取要在重定向中使用的服务器名称和端口。

相关内容

  • 没有找到相关文章

最新更新