简体   繁体   English

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

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

I'm creating a Hosted Blazor WASM app that is connecting to a Duende IdentityServer app for authentication and authorization.我正在创建一个托管 Blazor WASM 应用程序,该应用程序连接到 Duende IdentityServer 应用程序以进行身份验证和授权。 The Blazor Server part is functioning as BFF. Blazor 服务器部分用作 BFF。

The user is assigned a role and a role has permissions.用户被分配了一个角色,并且一个角色具有权限。 I want to add those permissions as a claim so I can use it for authorization purposes in Blazor app.我想将这些权限添加为声明,以便我可以在 Blazor 应用程序中将其用于授权目的。 I'm adding the permissions in a "PermissionsClaimsPrincipalFactory".我在“PermissionsClaimsPrincipalFactory”中添加权限。 In the "PermissionClaimProfileService" I'm adding all claims off the user to the context.IssuedClaims.在“PermissionClaimProfileService”中,我将用户的所有声明添加到 context.IssuedClaims。

When I click "Login" in the Blazor App, I'm redirected to the login page on the IdentityServer App.当我在 Blazor 应用程序中单击“登录”时,我被重定向到 IdentityServer 应用程序的登录页面。 After login I'm redirected to Blazor and I see the claims assigned to my user.登录后我被重定向到 Blazor 并且我看到分配给我的用户的声明。 But the claims I add in my Profile Service are not shown on that page.但是我在 Profile Service 中添加的声明并未显示在该页面上。

In my logs I can see that my Profile Service is returning all claims.在我的日志中,我可以看到我的 Profile Service 正在返回所有声明。 But then I don't see them on the client side.但后来我在客户端看不到它们。

I've tried several things, but I don't see what I'm doing wrong.我已经尝试了几件事,但我看不出我做错了什么。

This is what I see in the logs:这是我在日志中看到的:

Profile service returned the following claim types: sub preferred_username name amr auth_time idp email AspNet.Identity.SecurityStamp http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier TenantKey Permissions email_verified配置文件服务返回以下声明类型: sub preferred_username name amr auth_time idp email AspNet.Identity.SecurityStamp http: //schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

These are the claims shown in the browser:这些是浏览器中显示的声明:

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 Blazor APP Uri: https://localhost:7111 IdentityServer Uri: https://localhost:7193

IdentityServer Client Configuration 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"
   ]
} 

Identity Server Resources身份服务器资源

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

Identity Server Profile Service身份服务器配置文件服务

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

Identity Server Claims Principal Factory身份服务器声明主体工厂

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

IdentityServer Registration身份服务器注册

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 Server Startup Code 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 Client Show Claims 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>

Via the related answers for this question I've found the solution for me.通过这个问题的相关答案,我找到了适合我的解决方案。 At least I get the claims now...至少我现在收到索赔...

This answer led me to it: Custom Claims are not being accessed in client with identityserver 4.Net core 2.0这个答案让我想到了: 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