Кажется, что это не та функциональность, которую предлагает IdentityServer4 (любые противоречивые комментарии приветствуются). В итоге я использовал утверждения для передачи информации о культуре обратно моему клиенту. Итак, я создал класс, унаследованный от IProfileService
, поэтому я могу загрузить дополнительное утверждение JwtClaimTypes.Locale
в idToken. Однако кажется, что когда он запущен, он находится в другом контексте, чем пользователь, для которого он запускается, поэтому CultureInfo.CurrentCulture
установлен на другой языковой стандарт, чем я ожидал (например, пользовательский интерфейс был установлен pl-PL
, но внутри профиля service было установлено en-US
). Итак, я закончил тем, что создал класс InMemoryUserInfo
, который в основном представляет собой упакованный ConcurrentDictionary
, который содержит мой идентификатор пользователя и объект, содержащий выбранную пользователем локаль. Я создаю запись / обновляю этот словарь всякий раз, когда пользователь меняет предпочтительный язык или когда язык пользователя доставляется из базы данных. В любом случае, этот 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;
}
}
Не забудьте зарегистрировать IProfileService
с DI
services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();
Впоследствии, в мой клиентский стартап, я анализирую заявки в OpenIdConnectEvents
и устанавливаю для повара ie культуру, полученную от 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;
};
}
});