声明的状态管理如何在 Blazor Server 中工作?

Posted

技术标签:

【中文标题】声明的状态管理如何在 Blazor Server 中工作?【英文标题】:How State management of claims work in Blazor Server? 【发布时间】:2021-10-16 14:07:44 【问题描述】:

我已经为身份验证代码流实现了身份服务器。

坚持声明的正确方法是什么(在OnTicketReceivedOnTicketValidated 中,如下所示),以便在后续调用中 到 Blazor 页面,我可以收到 User aka ClaimPrincipal 填充 我的用途?

这是我的资源服务器的中间件代码:

    public void ConfigureServices(IServiceCollection services)
    
          //....

        services.AddIdentity<ApplicationUser, IdentityRole>(options =>
          
              options.SignIn.RequireConfirmedAccount = false;
              options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);

              options.SignIn.RequireConfirmedEmail = false;
          )
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<SomeContext>()
            .AddDefaultTokenProviders();

中间件集成:

       services.AddAuthentication(options =>
        
            options.DefaultScheme = "cookie";
            options.DefaultChallengeScheme = "oidc";
            options.DefaultSignOutScheme = "oidc";
        )
            .AddCookie("cookie", options =>
            
                options.Cookie.Name = "__Host-bff";
                options.Cookie.SameSite = SameSiteMode.Strict;
            )
            .AddOpenIdConnect("oidc", options =>
            
                options.Authority = "https://localhost:5001";
                options.ClientId = "mvc.code";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.ResponseMode = "query";

                options.GetClaimsFromUserInfoEndpoint = true;
                options.MapInboundClaims = false;
                options.SaveTokens = true;

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                

                //Critical Parts
                options.TokenValidationParameters = new()
                
                    NameClaimType = "name",
                    RoleClaimType = "role"
                ;

来自身份服务器的认证回调如下:

                options.Events.OnTicketReceived = async n =>
                
                    var serviceProvider = n.HttpContext.RequestServices;
                    var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");

                    

我尝试使用 BlazoredSessionStorage 等,但似乎还为时过早 调用它。我们必须等到 OnPrerender 或 OnInit

我也试过CustomTokenStore。但是cookie的索赔如何 回到服务器?

           var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
                  
                    if (n.Principal != null)
                    
                        var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;


                        await accountService.UserCreateAsync(new NewAccount
                        
                            Username = userName,
                            FirstName = userFirstName,
                            LastName = userLastName,
                            //ContactId = 100,
                            TenantId = 1
                        );
                        await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin  Username = userName , 24 * 60);
                    
                ;

 public class CustomTokenStore : IUserAccessTokenStore

    ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();

    public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
    
        var sub = user.FindFirst("sub").Value;
        _tokens.TryRemove(sub, out _);
        return Task.CompletedTask;
    

    public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
    
        var sub = user.FindFirst("sub").Value;
        _tokens.TryGetValue(sub, out var value);
        return Task.FromResult(value);
    

    public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
    
        var sub = user.FindFirst("sub").Value;
        var token = new UserAccessToken
        
            AccessToken = accessToken,
            Expiration = expiration,
            RefreshToken = refreshToken
        ;
        _tokens[sub] = token;
        return Task.CompletedTask;
    

【问题讨论】:

【参考方案1】:
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            

                options.ClaimActions.Add(new CustomClaimsFactory(
                                        "userName",
                                        "XXXXX@outlook.com"
                                    ));
                options.TokenValidationParameters = new TokenValidationParameters
                
                    // Instead of using the default validation (validating against a single issuer value, as we do in
                    // line of business apps), we inject our own multitenant validation logic
                    ValidateIssuer = false,

                    // If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
                    //IssuerValidator = (issuer, securityToken, validationParameters) => 
                    //    if (myIssuerValidationLogic(issuer)) return issuer;
                    //
                ;

                options.Events = new OpenIdConnectEvents
                
                    OnTicketReceived = context =>
                    
                        // If your authentication logic is based on users then add your logic here
                        return Task.CompletedTask;
                    ,
                    OnAuthenticationFailed = context =>
                    
                        context.Response.Redirect("/Error");
                        context.HandleResponse(); // Suppress the exception
                        return Task.CompletedTask;
                    ,
                    // If your application needs to do authenticate single users, add your user validation below.
                    //OnTokenValidated = context =>
                    //

                    //     var claims = new List<Claim>
                    //     
                    //        new Claim(ClaimTypes.Role, "superadmin")
                    //     ;
                    //    var appIdentity = new ClaimsIdentity(claims);
                    //    context.Principal.AddIdentity(appIdentity);
                    //    return Task.CompletedTask;
                    //    //return myUserValidationLogic(context.Ticket.Principal);
                    //
                ;
            );

虽然 CustomActionFactory 类定义如下

public class CustomClaimsFactory : ClaimAction
    
        string _ClaimType;
        string _ValueType;
        public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
        
            _ClaimType = claimType;
            _ValueType = valueType;
        
        public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
        
            identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));

        
    ```

【讨论】:

【参考方案2】:

OnTokenValidated 有一种方法可以添加您使用声明创建的新身份:

OnTokenValidated = ctx => ctx.Principal.AddIdentity(myNewClaim); 

【讨论】:

请问我们如何在调用服务器时将其取回? 在 OnTokenValidated 回调中添加声明后,声明将在身份用户下可用。 很抱歉,Identity User 没有声明。这是问题的目标。 @Abhijeet,我猜你已经关注了this link,你可以尝试添加 IdentityResource

以上是关于声明的状态管理如何在 Blazor Server 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

Blazor和Vue对比学习(进阶2.2.1):状态管理概述

服务器端 Blazor:刷新页面后如何保持用户身份验证?

如何修改 Blazor(服务器)中的当前文化日期格式?

Blazor:如何存储页面状态(所有组件状态)?

从零开始Blazor Server--基于策略的权限验证

Blazor中的无状态组件