0

我正在关注 Identity Server 快速入门模板,并尝试设置以下内容

  • 身份服务器 aspnet 核心应用程序
  • Mvc 客户端,它向 is4 进行身份验证,并调用 webapi 客户端,它是一个受保护的 api 资源。

有一个额外的ApplicationUser列,我添加到ProfileService这样的声明中:

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var sub = context.Subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(sub);
            if (user == null)
                return;

            var principal = await _claimsFactory.CreateAsync(user);
            if (principal == null)
                return;

            var claims = principal.Claims.ToList();

            claims.Add(new Claim(type: "clientidentifier", user.ClientId ?? string.Empty));

            // ... add roles and so on

            context.IssuedClaims = claims;
        }

最后是 Mvc Client appConfigureServices方法中的配置:

            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ClientId = "mvc";
                options.ClientSecret = "mvc-secret";
                options.ResponseType = "code";

                options.SaveTokens = true;

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

                options.Scope.Add("api1");

                options.GetClaimsFromUserInfoEndpoint = true;

                options.ClaimActions.MapUniqueJsonKey("clientidentifier", "clientidentifier");
            });

GetClaimsFromUserInfoEndpoint设置为true我可以访问 中的自定义声明,User.Identity但这会导致 2 次调用ProfileService

如果我删除或设置为 false,则此声明仍然是 access_token 的一部分,但不是 id_token 的一部分,然后我无法从上下文用户访问此特定声明。

有没有更好的方法可以从用户主体访问此声明而不会导致 2 次调用(就像现在一样)?或者也许从上下文中读取 access_token 并在检索到令牌后更新用户声明?

谢谢 :)

4

4 回答 4

3

事实证明,Client身份服务器中的对象具有完成这项工作的这个属性:

        //
        // Summary:
        //     When requesting both an id token and access token, should the user claims always
        //     be added to the id token instead of requring the client to use the userinfo endpoint.
        //     Defaults to false.
        public bool AlwaysIncludeUserClaimsInIdToken { get; set; }

正如 lib 元数据中所解释的那样,为客户端将其设置为 true,那么客户端没有必要去从端点重新获取声明

谢谢大家 :)

于 2020-04-13T13:36:38.450 回答
1

如果您想在客户端访问自定义声明而不是身份服务器中添加的声明,只需按照以下步骤操作,它对我有用。我想你在 asp.net 核心中将客户端和身份服务器都实现为独立的项目并且它们已经准备好了,你现在想玩声明或者可能想通过角色声明等进行授权,好吧,让我们开始吧

  1. 创建一个继承自“IClaimsTransformation”的类,如下所示:
public class MyClaimsTransformation : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            var userName = principal.Identity.Name;
            var clone = principal.Clone();
            var newIdentity = (ClaimsIdentity)clone.Identity;
            var user = config.GetTestUsers().Where(p => p.Username == userName).First();
            if (user != null)
            {
                var lstUserClaims = user.Claims.Where(p => p.Type == JwtClaimTypes.Role).ToList();
                foreach (var item in lstUserClaims)
                    if (!newIdentity.Claims.Where(p => p.ValueType == item.ValueType && p.Value == item.Value).Select(p => true).FirstOrDefault())
                        newIdentity.AddClaim(item);
            }
            return Task.FromResult(principal);
        }
    }

但请注意,此类将通过用户身份验证多次调用,因此我添加了一个简单的代码来防止多次重复声明。您也有经过身份验证的用户的用户名。

  1. 接下来创建另一个像这样的类:
public class ProfileService : IProfileService
    {
        //private readonly UserManager<ApplicationUser> userManager;

        public ProfileService(/*UserManager<ApplicationUser> userManager*/ /*, SignInManager<ApplicationUser> signInManager*/)
        {
            //this.userManager = userManager;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            context.AddRequestedClaims(context.Subject.Claims);

            var collection = context.Subject.Claims.Where(p => p.Type == JwtClaimTypes.Role).ToList();
            foreach (var item in collection)
            {
                var lst = context.IssuedClaims.Where(p => p.Value == item.Value).ToList();
                if (lst.Count == 0)
                    context.IssuedClaims.Add(item);
            }

            await Task.CompletedTask;
        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            //context.IsActive = true;
            await Task.FromResult(0); /*Task.CompletedTask;*/
        }
    }

此类将通过多个上下文调用,但没关系,因为我们在此代码的第 1 部分添加了自定义声明

foreach (var item in lstUserClaims)
   if (!newIdentity.Claims.Where(p => p.ValueType == item.ValueType && p.Value == item.Value).Select(p => true).FirstOrDefault())
        newIdentity.AddClaim(item);
  1. 这是您在身份服务器端的基本 startup.cs:
public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options => options.EnableEndpointRouting = false);
            services.AddTransient<Microsoft.AspNetCore.Authentication.IClaimsTransformation, MyClaimsTransformation>();
            services.AddIdentityServer().AddDeveloperSigningCredential()
                    .AddInMemoryApiResources(config.GetApiResources())
                    .AddInMemoryIdentityResources(config.GetIdentityResources())
                    .AddInMemoryClients(config.GetClients())
                    .AddTestUsers(config.GetTestUsers())
                    .AddInMemoryApiScopes(config.GetApiScope())
                    .AddProfileService<ProfileService>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseIdentityServer();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();

        }
    }

注意.AddProfileService<ProfileService>();services.AddTransient<Microsoft.AspNetCore.Authentication.IClaimsTransformation, MyClaimsTransformation>();

  1. 现在在客户端转到 startup.cs 并执行以下操作:
.AddOpenIdConnect("oidc", options =>
{
   //other code
   options.GetClaimsFromUserInfoEndpoint = true;
   options.ClaimActions.Add(new JsonKeyClaimAction(JwtClaimTypes.Role, null, JwtClaimTypes.Role));
})

对于我的示例,我尝试使用“角色”并通过我的自定义角色授权用户。

  1. 接下来在您的控制器类中这样做: [Authorize(Roles = "myCustomClaimValue")]或者您可以为自定义授权过滤器创建一个类。

请注意,您在身份服务器项目的配置文件中定义了测试用户,并且该用户具有这样的自定义声明new claim(JwtClaimTypes.Role, "myCustomClaimValue"),这将返回lstUserClaims变量。

于 2022-01-12T12:38:37.850 回答
0

这是有关该主题的一些额外信息。默认情况下,IdentityServer 不会在身份令牌中包含身份声明。通过将客户端配置上的 AlwaysIncludeUserClaimsInIdToken 设置设置为 true 来允许。但不建议这样做。初始身份令牌通过表单发布或 URI 通过前端通道通信从授权端点返回。如果它通过 URI 返回并且令牌变得太大,您可能会遇到 URI 长度限制,这仍然取决于浏览器。大多数现代浏览器不存在长 URI 问题,但像 Internet Explorer 这样的旧浏览器可能会出现问题。这可能会或可能不会让您担心。看起来我的项目和你的很相似。祝你好运。

于 2020-04-15T01:46:31.977 回答
0

我假设您在调用 API 时将 Authorization 标头与 Bearer JWT 令牌一起传递。您可以从 API 控制器中的 HttpContext 读取 access_token。

  var accessToken = await this.HttpContext.GetTokenAsync("access_token");
    var handler = new JwtSecurityTokenHandler();

    if (handler.ReadToken(accessToken) is JwtSecurityToken jt && (jsonToken.Claims.FirstOrDefault(claim => claim.Type == "sub") != null))
    {
        var subID = jt.Claims.FirstOrDefault(claim => claim.Type == "sub").Value;
    }

注意:GetClaimsFromUserInfoEndpoint 不需要明确设置。

于 2020-04-11T18:51:16.663 回答