Вернуть "ui_locale" обратно клиенту - PullRequest
0 голосов
/ 02 августа 2020

Итак, я знаю, как заставить приложение IdentityServer4 использовать культуру, присущую требовательному клиенту. Определив

options.Events = new OpenIdConnectEvents
{
   OnRedirectToIdentityProvider = context =>
   {
      context.ProtocolMessage.UiLocales = "pl-PL";
      return Task.CompletedTask;
   },                     
}

, я могу заставить IdentityServer4 также показывать мне страницу входа в «pl-PL». Уловка, однако, заключается в том, что я разрешаю пользователям изменять язык на экране входа в систему. Как я могу сообщить клиенту, что информация о культуре была изменена во время входа в систему? В настоящее время мой клиент даже не отображает ни одной страницы, переходит непосредственно к экрану входа в систему (таким образом, браузер клиентского приложения немедленно перенаправляется в приложение IdentityServer4, где пользователь может изменить свой язык).

1 Ответ

0 голосов
/ 09 августа 2020

Кажется, что это не та функциональность, которую предлагает 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;
        };
    }
});
...