如何使用自定义用户服务为IdentityServer3添加访问令牌声明

5

我想创建一个自定义用户,对其进行身份验证,然后使用Identity Server 3将用户声明返回到Angular应用程序中。我已经查看了示例,特别是CustomUserService项目。

编辑:根据进展更新了问题:

在startup.cs文件中,我加载了范围和客户端。

var idServerServiceFactory = new IdentityServerServiceFactory()
                    .UseInMemoryClients(Clients.Get())
                    .UseInMemoryScopes(Scopes.Get());
                   //.UseInMemoryUsers(Users.Get());

接下来是在UserServices.cs文件中的重写部分。

public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
        {
            string hashedPassword = string.Empty;
        string salt = string.Empty;

        //pull users 
        using (IdentityModelContext ctx = new IdentityModelContext())
        {
            IIdSrvUserRepository ur = new IdSrvUserRepository(ctx);
            Users = ur.GetAll().ToList();
           }
        //salt curent password        
        var user = Users.SingleOrDefault(x => x.UserName == context.UserName);
        if (user != null)
        {
            salt = user.Salt;
            hashedPassword = IqUtils.GenerateSaltedPassword(context.Password, salt);
            if (user.UserName == context.UserName && hashedPassword == user.Password)
            {
                try
                {
                    context.AuthenticateResult = new AuthenticateResult(user.Subject, user.UserName);
                }
                catch (Exception ex)
                {
                    string msg = $"<-- Login Error: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
                    myLogger log = new myLogger("IdentityServer");
                }
            }          
        }
        return Task.FromResult(0);
    }

我遇到困难的地方在于GetProfileDataAsync方法。我无法弄清如何向客户端添加声明,或者是否需要调用第二个客户端。

该应用程序是一个通过Idsrv进行身份验证的angular客户端。我已创建了一个客户端。

//Angular client authentication
                new Client 
                {
                    ClientId = "iqimplicit",
                    ClientName = "IQ Application (Implicit)",
                    Flow = Flows.Implicit, 
                    AllowAccessToAllScopes = true,
                    IdentityTokenLifetime = 300,//default value is 5 minutes this is the token that allows a user to login should only be used once
                    AccessTokenLifetime = 3600, // default is one hour access token is used for securing routes and access to api in IQ
                    RequireConsent = false,
                    RequireSignOutPrompt = true,
                    AllowedScopes = new List<string>
                    {
                       //no clue if this is correct
                        StandardScopes.Profile.Name

                    },
                    RedirectUris = new List<string>

                    { 
                        angularClient + "callback.html"
                    },  
                PostLogoutRedirectUris = new List<string>()
                {
                    //redirect to login screen
                    angularClient 
                }
                }

根据这里的SO帖子,我正在尝试将声明添加到返回给客户端的访问令牌中。

我定义的范围只适用于资源。 我认为我需要创建一个类似于被注释掉的部分的身份第二个范围?

            return new List<Scope>
                { 
                    StandardScopes.OpenId,
                    StandardScopes.ProfileAlwaysInclude,
                    StandardScopes.Address,  

                    new Scope
                    { 
                        Name = "appmanagement",
                        DisplayName = "App Management",
                        Description = "Allow application to management.",
                        Type = ScopeType.Resource,
                        Claims = new List<ScopeClaim>()
                        {
                            new ScopeClaim("role", false),
                            new ScopeClaim("Name", true),
                            new ScopeClaim("GivenName", true),
                            new ScopeClaim("FamilyName", true),
                            new ScopeClaim("Email", true),
                        },
                    },

                //    new Scope
                //    {
                //        Name = "iquser",
                //        DisplayName = "User",
                //        Description = "Identifies the user",
                //        Type = ScopeType.Identity,
                //        Claims = new List<ScopeClaim>()
                //        {
                //            new ScopeClaim("Name", true)
                //        }
                //    }
                };
        }
    }
}

我希望传递回去的信息如上所示,包括姓名、名字、姓氏和电子邮件。

尝试遵循之前提到的 SO 帖子中的评论,覆盖了 GetProfileDataAsync 方法,该方法应该获取用户的信息。

  public override Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                // issue the claims for the user
                var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
                if (user != null)
                {
                    if(context.RequestedClaimTypes != null)
                        try
                        {
                            if (context.RequestedClaimTypes != null)
                            {
                                List<Claim> newclaims = new List<Claim>();
                                foreach (Claim claim in context.Subject.Claims)
                                {
                                    if (context.RequestedClaimTypes.Contains(claim.Type))
                                    {
                                        newclaims.Add(claim);
                                    }
                                }
                                context.IssuedClaims = newclaims;
                            }                        
                        }
                        catch (Exception ex)
                        {    
                            string msg = $"<-- Error Getting Profile: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
                            myLogger log = new myLogger("IdentityServer");
                        }
                }
                //return Task.FromResult(context.IssuedClaims);
                return Task.FromResult(0);
            }
        }

我现在遇到了困难,尽管我已经看到了我定义的 context.RequestedClaimTypes 中所述的声明,但是在 claim.Type 中从来没有匹配上,因为它总是包含idsrv声明的基础属性。

由于这是一个自定义数据库,所有发生在claims中的信息都存储在数据库的Users表中,而不是Claims表中。 我试图将用户表中每个字段的值映射到Claim值。

更新 我已经回到了范围并将以下声明添加到标识范围类型中。

new Scope
                    {
                        Name = "iuser",
                        DisplayName = "User",
                        Description = "Identifies the  user",
                        Type = ScopeType.Identity,
                        Claims = new List<ScopeClaim>()
                        {
                            new ScopeClaim(Constants.ClaimTypes.Name, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.GivenName, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.FamilyName, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.Email, alwaysInclude: true),
                        }
                    }

初始身份验证已经完成,回调函数中可以看到作用域的评估。然而,我仍然困惑于如何获取应用于这些作用域的数据库中的值。代码的这一部分(如预期的那样)正在寻找 System.Security.Claims.Claim 类型。这是确定如何获取用户属性作为声明应用和返回的声明值的停止点。

此时,我被重定向回了 Angular 应用程序,但是 accesstoken 对象为空,没有用户信息。

在这一点上,我该如何将自己的值作为声明插入到 context.IssuedClaims 中?

提前致谢。

3个回答

2

对于您似乎正在尝试做的事情,我有几个评论。

1 您正在复制现有范围,即 profile 范围。

如果您想要的所有声明都在 OIDC 标准范围内(这里是 profile / email),则无需定义自己的 iuser 范围。您在 iuser 身份范围中声明存在的声明已由标准中的 profile 范围涵盖。因此,请直接请求 profile 范围,并使您的客户端能够请求该范围。

标准范围及其声明内容: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims

2 您的客户端配置中的 AllowedScopes 配置有点错误。

请改用以下常量。

AllowedScopes = new List<string>
{
    Constants.StandardScopes.OpenId,
    Constants.StandardScopes.Profile,
    Constants.StandardScopes.Email,
}

请参考这个配置示例:https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/_sharedConfiguration/Clients.cs 3.您还没有展示如何获取id_tokens...
但请记住,向idsrv提供请求时,您需要在scope参数中包括一个值为openid profile的声明,以便从profile范围返回id_token。换句话说,向/authorize, /userinfo/token端点发送请求必须包含一个scope参数。
例如,像Brock A.在他的oidc js客户端库中所做的一样: https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/Clients/JavaScriptImplicitClient/app.js#L18

根据您的回应,索赔的值将从包含这些值的数据库表(如AspNetIdentity)中派生?我的方法不同之处在于,这些值都存储为用户表上的属性。(没有身份验证数据库)正确的方法是在Scope.cs类中派生这些值并在那里添加它们吗?由于它们实际上是自定义属性,因此我不需要将它们映射到标准作用域,但反过来可以传递自定义作用域?在我的当前方法(上面的代码)中,索赔是通过资源客户端而不是标识传递的。(我知道这是不正确的)。 - rlcrews
你如何映射值到声明是IUserService实现的目的。 - John Korsnes
在该示例中,请求的声明(由您请求的范围定义)与内存列表“Users”匹配。通常,这些是从数据库获取的 - 就像在您的情况下:用户表。 - John Korsnes

1
乍一看,似乎您的Angular客户端未配置请求资源范围iuser,您仅配置请求name身份范围。将该范围添加到客户端iqimplicit的允许范围列表中,并确保将该范围添加到您的Angular应用程序的客户端配置中。

1
通过进一步的工作,我终于能够找出一种添加声明的方法。
UserService类的GetProfileDataAsync()中,我最终成功创建并添加了一个声明,具体如下。
var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
            if (user != null)
            {
                    try
                    {
                       //add subject as claim
                        var claims = new List<Claim>
                        {
                         new Claim(Constants.ClaimTypes.Subject, user.Subject),
                        };
                        //get username
                        UserClaim un = new UserClaim
                        {
                            Subject = Guid.NewGuid().ToString(),
                            ClaimType = "username",
                            ClaimValue = string.IsNullOrWhiteSpace(user.UserName) ?  "unauth" : user.UserName
                        };
                        claims.Add(new Claim(un.ClaimType, un.ClaimValue));
                        //get email
                        UserClaim uem = new UserClaim
                        {
                            Subject = Guid.NewGuid().ToString(),
                            ClaimType = "email",
                            ClaimValue = string.IsNullOrWhiteSpace(user.EmailAddress) ? string.Empty : user.EmailAddress
                        };
   context.IssuedClaims = claims;

                    }
                    catch (Exception ex)
                    {

                        string msg = $"<-- Error Getting Profile: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
                        myLogger log = new myLogger("IdentityServer");
                    }

            }
            //return Task.FromResult(context.IssuedClaims);
            return Task.FromResult(0);

我遇到的问题更多地涉及模型。通常保存在Claims存储中的所有信息都存储在用户本身上,因此当创建配置文件时,我必须从用户那里提取值并将其作为声明创建/添加。
Angular客户端中的OIDC管理器然后使用以下内容授予我访问令牌上的声明及其值:
 $scope.mgr.oidcClient.loadUserProfile($scope.mgr.access_token)
                    .then(function (userInfoValues) {
                        $scope.profile = userInfoValues;
                        $scope.fullName = $scope.profile.given_name + ' ' + $scope.profile.family_name;
                        checkAdmin($scope.profile.sys_admin);
                        $scope.$apply();
                    });

请注意,这里仅使用$apply()是因为该代码存在于应用程序中使用的指令内部。
我不确定这是否是添加声明的最佳实现方式,但这种方法确实有效。暂时我会等待更好的方法再标记它作为答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接