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;
        };
    }
});