Return "ui_locale" 回客户端
Return "ui_locale" back to client
所以我知道如何让 IdentityServer4 应用程序使用具有挑战性的客户所拥有的文化。通过定义
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.UiLocales = "pl-PL";
return Task.CompletedTask;
},
}
我可以让 IdentityServer4 也显示“pl-PL”中的登录页面。然而,诀窍是我允许用户更改登录屏幕上的语言。我如何通知客户登录期间更改了文化信息?
目前我的客户端甚至不显示任何页面,直接进入登录屏幕(因此从客户端应用程序浏览器立即重定向到 IdentityServer4 应用程序,用户可以在其中更改 his/her 语言)。
这似乎不是 IdentityServer4 提供的功能(欢迎任何矛盾的评论)。所以我最终使用声明将文化信息传递回我的客户。
所以我创建了一个继承自 IProfileService
的 class,这样我就可以将额外的声明 JwtClaimTypes.Locale
加载到 idToken。然而,当它是 运行 时,它与它运行的用户处于不同的上下文中,因此 CultureInfo.CurrentCulture
设置为与我预期的不同的区域设置(例如 UI 设置为 pl-PL
但在配置文件服务中,它设置为 en-US
)。所以我最终创建了一个 InMemoryUserInfo
class ,它基本上是一个包含我的用户 ID 的包装 ConcurrentDictionary
和一个包含用户选择的语言环境的对象。每当用户更改首选语言或从数据库提供用户语言时,我都会创建 entry/update 该词典。无论如何,然后 InMemoryUserInfo
被注入到我的配置文件服务中,并在其中添加为另一个声明:
public class IdentityWithAdditionalClaimsProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
/// <summary>
/// This services is running in a different thread then UI, so
/// when trying to obtain CultureInfo.CurrentUICulture, it not necessarily
/// is going to be correct. So whenever culture is changed,
/// it is stored in InMemoryUserInfo. Current user's culture will
/// be included in a claim.
/// </summary>
private readonly InMemoryUserInfo _userInfo;
public IdentityWithAdditionalClaimsProfileService(
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
UserManager<ApplicationUser> userManager,
InMemoryUserInfo userInfo)
{
_claimsFactory = claimsFactory;
_userManager = userManager;
_userInfo = userInfo;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
claims.Add(new Claim(JwtClaimTypes.Locale, _userInfo.Get(user.Id).Culture ?? throw new ArgumentNullException()));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
记得用DI
注册IProfileService
services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();
之后,在我的客户端启动时,我分析了 OpenIdConnectEvents
中的声明并将 cookie 设置为从 IdentityServer 接收到的文化:
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
//Goes through returned claims from authentication endpoint and looks for
//localization info. If found and different, then new CultureInfo is set.
string? culture = context.Principal?.FindFirstValue(JwtClaimTypes.Locale);
if (culture != null && CultureInfo.CurrentUICulture.Name != culture)
{
context.HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)),
new CookieOptions
{ Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
return Task.CompletedTask;
};
}
});
所以我知道如何让 IdentityServer4 应用程序使用具有挑战性的客户所拥有的文化。通过定义
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.UiLocales = "pl-PL";
return Task.CompletedTask;
},
}
我可以让 IdentityServer4 也显示“pl-PL”中的登录页面。然而,诀窍是我允许用户更改登录屏幕上的语言。我如何通知客户登录期间更改了文化信息? 目前我的客户端甚至不显示任何页面,直接进入登录屏幕(因此从客户端应用程序浏览器立即重定向到 IdentityServer4 应用程序,用户可以在其中更改 his/her 语言)。
这似乎不是 IdentityServer4 提供的功能(欢迎任何矛盾的评论)。所以我最终使用声明将文化信息传递回我的客户。
所以我创建了一个继承自 IProfileService
的 class,这样我就可以将额外的声明 JwtClaimTypes.Locale
加载到 idToken。然而,当它是 运行 时,它与它运行的用户处于不同的上下文中,因此 CultureInfo.CurrentCulture
设置为与我预期的不同的区域设置(例如 UI 设置为 pl-PL
但在配置文件服务中,它设置为 en-US
)。所以我最终创建了一个 InMemoryUserInfo
class ,它基本上是一个包含我的用户 ID 的包装 ConcurrentDictionary
和一个包含用户选择的语言环境的对象。每当用户更改首选语言或从数据库提供用户语言时,我都会创建 entry/update 该词典。无论如何,然后 InMemoryUserInfo
被注入到我的配置文件服务中,并在其中添加为另一个声明:
public class IdentityWithAdditionalClaimsProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
/// <summary>
/// This services is running in a different thread then UI, so
/// when trying to obtain CultureInfo.CurrentUICulture, it not necessarily
/// is going to be correct. So whenever culture is changed,
/// it is stored in InMemoryUserInfo. Current user's culture will
/// be included in a claim.
/// </summary>
private readonly InMemoryUserInfo _userInfo;
public IdentityWithAdditionalClaimsProfileService(
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
UserManager<ApplicationUser> userManager,
InMemoryUserInfo userInfo)
{
_claimsFactory = claimsFactory;
_userManager = userManager;
_userInfo = userInfo;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
claims.Add(new Claim(JwtClaimTypes.Locale, _userInfo.Get(user.Id).Culture ?? throw new ArgumentNullException()));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
记得用DI
注册IProfileService
services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();
之后,在我的客户端启动时,我分析了 OpenIdConnectEvents
中的声明并将 cookie 设置为从 IdentityServer 接收到的文化:
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
//Goes through returned claims from authentication endpoint and looks for
//localization info. If found and different, then new CultureInfo is set.
string? culture = context.Principal?.FindFirstValue(JwtClaimTypes.Locale);
if (culture != null && CultureInfo.CurrentUICulture.Name != culture)
{
context.HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)),
new CookieOptions
{ Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
return Task.CompletedTask;
};
}
});