緣起
說明:名字用Abp vNext是因為這屬於我Abp vNext系列的文章,該問題其實應該屬於ids4的部署問題和abp沒啥關系。
問題源於周五晚上在和群友聊的時候聊到使用Nginx反向代理后端來做SSL,本來覺得這個問題很簡單我就直接部署了一個沒想到這就碰到問題了,我的ids4
的issuer出問題了。
大家可以看下面這幅圖,我的Scheme分明是Https但是到了issuer卻變成了http。
下面是我的nginx,我把scheme
和X-Forwarded-For
都做了配置,但是后端還是拿不到正確的scheme
。
初步方案
我之前我仔細閱讀過老張的ids4系列,我記得他講過這方面的東西,然后我就去翻閱了他的博客,通過手動修改IssuerUri
來解決。
研究文檔
我在官方文檔看到了這么一句話(建議不要設置此屬性,該屬性根據主機名推斷頒發者名稱),我又加以想想發現這樣寫會存在一個問題,如果我的項目像被多個域名映射豈不是直接歇菜了(當然這個是可以配置的,但是我的想法是約定大於配置,盡量不要去更改這些東西)。
查找根源
我決定自己從源頭來看看到底什么原因,我在IdentityServer4
的源碼中找到了下面代碼,在此我們知道了為啥設置IssuerUri
會生效,另外我們也看到GetIdentityServerOrigin
中的Http來自於context.Request.Scheme
;
/// <summary>
/// Gets the identity server issuer URI.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">context</exception>
public static string GetIdentityServerIssuerUri(this HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
// if they've explicitly configured a URI then use it,
// otherwise dynamically calculate it
var options = context.RequestServices.GetRequiredService<IdentityServerOptions>();
var uri = options.IssuerUri;
if (uri.IsMissing())
{
uri = context.GetIdentityServerOrigin() + context.GetIdentityServerBasePath();
if (uri.EndsWith("/")) uri = uri.Substring(0, uri.Length - 1);
if (options.LowerCaseIssuerUri)
{
uri = uri.ToLowerInvariant();
}
}
return uri;
}
public static string GetIdentityServerOrigin(this HttpContext context)
{
var options = context.RequestServices.GetRequiredService<IdentityServerOptions>();
var request = context.Request;
if (options.MutualTls.Enabled && options.MutualTls.DomainName.IsPresent())
{
if (!options.MutualTls.DomainName.Contains("."))
{
if (request.Host.Value.StartsWith(options.MutualTls.DomainName, StringComparison.OrdinalIgnoreCase))
{
return request.Scheme + "://" +
request.Host.Value.Substring(options.MutualTls.DomainName.Length + 1);
}
}
}
return request.Scheme + "://" + request.Host.Value;
}
/// <summary>
/// Gets the base path of IdentityServer.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public static string GetIdentityServerBasePath(this HttpContext context)
{
return context.Items[Constants.EnvironmentKeys.IdentityServerBasePath] as string;
}
尋找方案
我在博客園找到了LouieGuo
的一篇文章,他給出了一個官方issues:https://github.com/dotnet/AspNetCore.Docs/issues/2384
其實主要問題就在於proxy_pass http://172.17.0.8:8000;
我們請求會被轉發,這里就變成了http請求了,導致context.Request.Scheme
拿到的就是http,解決方案是配置雙方(nginx/kestrel)的 X-Forwarded-Proto 讓其正確地識別實際用戶發出的協議是 http 還是 https。
代碼
在 OnApplicationInitialization
方法中加入
var forwardOptions = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
};
forwardOptions.KnownNetworks.Clear();
forwardOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardOptions);
反饋
@喝口開水
@MrChuJiu nginx反向代理后,實際取得的request.scheme 確實是難以避免的
官方文檔雖然說建議不要修改,我認為,還是要結合的你的實際場景。
另外,如果把宿主程序設為https,nginx反向代理到了https的宿主程序。這樣雖然可以解決scheme問題,但是額外就是性能損耗
還要浪費多余的CPU時間去加密、解密、https的握手,也帶來了帶多的損耗
所以,要么就簡單一點,直接改配置
要么就用你最后的那個forward方案
反思
這是目前我的一個解決方案,這個方案說實話不是很好,因為對代碼有污染,希望大佬如果有更好的方案給我留言蟹蟹!
也歡迎大家閱讀我的Abp vNext系列教程
聯系作者:加群:867095512 @MrChuJiu