繁体   English   中英

如何将自定义声明从 Duende IdentityServer 发送到 Blazor WASM 托管应用程序

[英]How to send custom claims from Duende IdentityServer to Blazor WASM Hosted app

我正在创建一个托管 Blazor WASM 应用程序,该应用程序连接到 Duende IdentityServer 应用程序以进行身份验证和授权。 Blazor 服务器部分用作 BFF。

用户被分配了一个角色,并且一个角色具有权限。 我想将这些权限添加为声明,以便我可以在 Blazor 应用程序中将其用于授权目的。 我在“PermissionsClaimsPrincipalFactory”中添加权限。 在“PermissionClaimProfileService”中,我将用户的所有声明添加到 context.IssuedClaims。

当我在 Blazor 应用程序中单击“登录”时,我被重定向到 IdentityServer 应用程序的登录页面。 登录后我被重定向到 Blazor 并且我看到分配给我的用户的声明。 但是我在 Profile Service 中添加的声明并未显示在该页面上。

在我的日志中,我可以看到我的 Profile Service 正在返回所有声明。 但后来我在客户端看不到它们。

我已经尝试了几件事,但我看不出我做错了什么。

这是我在日志中看到的:

配置文件服务返回以下声明类型: sub preferred_username name amr auth_time idp email AspNet.Identity.SecurityStamp http: //schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

这些是浏览器中显示的声明:

amr
   pwd
sid
   5BB444DB7141399BFA6ED4D1A65B1AE6
sub
   5c009dae-d118-44e1-9a46-c119dcc31ff9
auth_time
   1662538078
idp
   local
name
    {Name}
email
    {Email}
bff:logout_url
    /bff/logout?sid=5BB444DB7141399BFA6ED4D1A65B1AE6
bff:session_expires_in
    1209598
bff:session_state
    EvCMn29HkOmdDzxlAqvwUGfj3u0DgxHCymQtn1tOw0U.4F818675DFBEC4B689B4E3159633443A

Blazor APP Uri: https://localhost:7111 IdentityServer Uri: https://localhost:7193

IdentityServer 客户端配置

"BlazorClient": {
   "AlwaysSendClientClaims": true,
   "ClientId": "app-blazor",
   "ClientSecrets": [
      "SuperSecretPassword"
   ],
   "ClientName": "App",
   "ClientUri": "https://localhost:7111/",
   "AllowedGrantTypes": [
      "authorization_code"
   ],
   "AllowOfflineAccess": true,
   "RedirectUris": [
      "https://localhost:7111/signin-oidc"
   ],
   "FrontChannelLogoutUri": "https://localhost:7111/signout-oidc",
   "PostLogoutRedirectUris": [
      "https://localhost:7111/signout-callback-oidc"
   ],
   "AllowedScopes": [
      "openid",
      "profile",
      "api1"
   ]
} 

身份服务器资源

 public static IEnumerable<IdentityResource> IdentityResources =>
    new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile()
    };

 public static IEnumerable<ApiScope> ApiScopes =>
    new List<ApiScope>
    {
            new ApiScope("api1", "MyAPI")
    };

身份服务器配置文件服务

public class PermissionClaimProfileService : ProfileService<ApplicationUser>
{
    public PermissionClaimProfileService(UserManager<ApplicationUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, 
        ILogger<ProfileService<ApplicationUser>> logger) : base(userManager, claimsFactory, logger)
    {
    }

    public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        await base.GetProfileDataAsync(context);

        var existingClaims = context.IssuedClaims;
        foreach(var claim in context.Subject.Claims)
        {
            if (!existingClaims.Select(c => c.Type).ToList().Contains(claim.Type))
                existingClaims.Add(claim);
        }
        context.IssuedClaims = existingClaims.ToList();
    }
}

身份服务器声明主体工厂

public class PermissionClaimsPrincipalFactory<TIdentityUser> : UserClaimsPrincipalFactory<TIdentityUser>
    where TIdentityUser : IdentityUser
{
    private readonly IClaimsCalculator _claimsCalculator;

    /// <summary>
    /// Needs UserManager and IdentityOptions, plus the two services to provide the permissions and dataKey
    /// </summary>
    /// <param name="userManager"></param>
    /// <param name="optionsAccessor"></param>
    /// <param name="claimsCalculator"></param>
    public PermissionClaimsPrincipalFactory(UserManager<TIdentityUser> userManager, IOptions<IdentityOptions> optionsAccessor,
        IClaimsCalculator claimsCalculator)
        : base(userManager, optionsAccessor)
    {
        _claimsCalculator = claimsCalculator;
    }

    /// <summary>
    /// This adds the permissions and, optionally, a multi-tenant DataKey to the claims
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TIdentityUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        var userId = identity.Claims.GetUserIdFromClaims();
        var claims = await _claimsCalculator.GetClaimsForUser(userId);
        identity.AddClaims(claims);

        return identity;
    }
}

身份服务器注册

services.AddScoped<IClaimsCalculator, ClaimsCalculator>();
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, PermissionClaimsPrincipalFactory<ApplicationUser>>();
services
    .AddIdentityServer(options =>
        {
            options.KeyManagement.Enabled = true;
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;

            // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
            options.EmitStaticAudienceClaim = true;
         })
     .AddConfigurationStore(options =>
         {
            options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("identityserver"), sql => sql.MigrationsAssembly(migrationsAssembly));
         })
     .AddOperationalStore(options =>
         {
             options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("identityserver"), sql => sql.MigrationsAssembly(migrationsAssembly));
             options.EnableTokenCleanup = true;
             options.TokenCleanupInterval = 1800;
         })
      .AddAspNetIdentity<ApplicationUser>()
      .AddProfileService<PermissionClaimProfileService>();

Blazor 服务器启动代码

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.

    var configuration = builder.Configuration;

    builder.Services.AddControllersWithViews();
    builder.Services.AddRazorPages();

    builder.Services.AddBff();

    builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = "cookie";
        options.DefaultChallengeScheme = "oidc";
        options.DefaultSignOutScheme = "oidc";
    })
    .AddCookie("cookie", options =>
    {
        options.Cookie.Name = "__Host-blazor";
        options.Cookie.SameSite = SameSiteMode.Strict;
    })
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = configuration.GetValue<string>("IdentityServer:Authority");

        options.ClientId = configuration.GetValue<string>("IdentityServer:ClientId");
        options.ClientSecret = configuration.GetValue<string>("IdentityServer:ClientSecret");
        options.ResponseType = "code";
        options.ResponseMode = "query";

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

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

    var app = builder.Build();

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseWebAssemblyDebugging();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseBlazorFrameworkFiles();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseBff();
    app.UseAuthorization();
    app.MapBffManagementEndpoints();

    app.MapRazorPages();
    app.MapControllers();
    app.MapFallbackToFile("index.html");

    app.Run();
}

Blazor 客户端显示声明

<AuthorizeView>
  <Authorized>
    <dl>
        @foreach (var claim in @context.User.Claims)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
        }
    </dl>

    @if(context.User.HasPermission(Permissions.CompanyProfile_ManagePermissions)){
        <p>User has permissions to manage permissions for company profile</p>
    }

    @if (context.User.HasPermission(Permissions.Property_Read))
    {
        <p>User has permissions to read properties</p>
    }
  </Authorized>
  <NotAuthorized>
    <h3>No session</h3>
  </NotAuthorized>
</AuthorizeView>

通过这个问题的相关答案,我找到了适合我的解决方案。 至少我现在收到索赔...

这个答案让我想到了: Custom Claims are not being accessible in client with identityserver 4.Net core 2.0

AlwaysIncludeUserClaimsInIdToken = true

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM