WCF 自定义验证器:如何从自定义验证器初始化“用户”对象

Posted

技术标签:

【中文标题】WCF 自定义验证器:如何从自定义验证器初始化“用户”对象【英文标题】:WCF Custom Validator: How to initialize a "User" object from custom validator 【发布时间】:2011-01-18 21:49:30 【问题描述】:

我有一个有效的自定义 UserNamePasswordValidator 调用我的 Oracle DB。

该类派生自 System.IdentityModel.Selectors.UserNamePasswordValidator,Validate() 方法返回 void。

我从数据库加载我的用户对象,一旦密码被验证,我想隐藏我的“用户”对象,以便服务在开展业务时可以访问它。在 ASP.NET / Java 领域,我会将其存储到会话中,或者可能是我的整个控制器类。如何从 WCF 中的验证器执行此操作?

或者,换句话说,在 WCF 领域中为服务设置自定义用户域对象的最佳实践是什么。

更新:这就是我解决它的方法。我在验证期间缓存了 User 对象,然后在 AuthorizatinPolicy 步骤中访问它。

  // this gets called after the custom authentication step where we loaded the User
  public bool Evaluate(EvaluationContext evaluationContext, ref object state)
  
     // get the authenticated client identity
     IIdentity client = GetClientIdentity(evaluationContext);

     User user;
     OraclePasswordValidator.users.TryGetValue(client.Name, out user);
     if(user != null) 
        // set the custom principal
        evaluationContext.Properties["Principal"] = user;
        return true;
     

     return false;
  

【问题讨论】:

我现在将我的用户对象 (IPrincipal) 加载到密码验证器中,将其缓存在静态字典中,并在 AuthoriziationPolicy 中获取它。请参阅上面的编辑。这是最好的方法吗?对于这样一个丰富的框架来说,这似乎是一个拼凑。 好吧,我在上面的编辑中搞砸了格式,无法修复。那应该是上面代码区的完整方法了。 【参考方案1】:

我不是 WCF 专家,但从我目前阅读和实施的内容来看,“正确”的做法是使用 Validator验证用户,然后实现IAuthorizationPolicy 来执行实际的授权。因此,在授权策略中,您将在当前线程上设置自定义主体。

为了能够转发来自用户名/密码验证的信息,您可以实现一个继承自 UserNameSecurityTokenAuthenticator 的安全令牌验证器。 SecurityTokenAuthenticator 将首先调用验证器,如果验证成功,它可以添加您的自定义授权策略并通过构造函数将用户信息发送到策略。这句话很长:

public class CustomUsernameSecurityTokenAuthenticator : UserNameSecurityTokenAuthenticator

    protected override bool CanValidateTokenCore(System.IdentityModel.Tokens.SecurityToken token)
    
        return (token is UserNameSecurityToken);
    

    protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
    
        var authorizationPolicies = new List<IAuthorizationPolicy>();

        try
        
            var userNameToken = token as UserNameSecurityToken;
            new CustomUserNameValidator().Validate(userNameToken.UserName, userNameToken.Password);

            var claims = new DefaultClaimSet(ClaimSet.System, new Claim(ClaimTypes.Name, userNameToken.UserName, Rights.PossessProperty));

            authorizationPolicies.Add(new CustomAuthorizationPolicy(claims));
        
        catch (Exception)
        
            authorizationPolicies.Add(new InvalidAuthorizationPolicy());
            throw;
        
        return authorizationPolicies.AsReadOnly();
    

这里有一篇文章对所涉及的类进行了更多描述; http://blogs.msdn.com/card/archive/2007/10/04/how-identity-providers-can-show-custom-error-messages-in-cardspace.aspx

【讨论】:

我将仔细查看您的答案并试一试,然后更新问题。将用户(主体)缓存到地图中立即解决了问题,我还没有时间重新审视那段代码,但你的解释是我见过的第一个合理的解释。 如何在管道中挂钩?此外,您使用 CustomUserNameValidator,是否也将其挂接到配置文件中。如果是这样,它不是被调用两次吗?这种方法是否会向客户端返回身份验证请求(如果使用基本身份验证)? 错误:CustomUsernameSecurityTokenAuthenticator' 没有实现继承的抽象成员 'System.IdentityModel.Selectors.UserNameSecurityTokenAuthenticator.ValidateUserNamePasswordCore(string, string)'【参考方案2】:

我也有同样的问题。

我正在使用 API 连接到我的底层 Oracle 数据库,并通过打开连接来“验证”登录详细信息。

然后我想将此连接存储在某处(很简单,我将为所有不同的用户创建一个连接池),但还创建一个代表此用户的自定义身份和主体,以便一旦它到达我的自定义 IAuthorizationPolicy,它不需要重新加载此信息。

我做了很多搜索,但没有找到任何东西,所以我的计划是这样做:

    通过打开 API 连接在自定义 UserNamePasswordValidator 中验证登录详细信息。

    将打开的连接存储在用户名下的连接池中。

    当我的自定义 IAuthorizationPolicy.Evaluate() 被调用时,我将查看提供的通用标识:

    IIdentity GetClientIdentity(EvaluationContext evaluationContext)
    
        object obj;
        if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
            throw new Exception("No Identity found");
    
           IList<IIdentity> identities = obj as IList<IIdentity>;
           if (identities == null || identities.Count <= 0)
              throw new Exception("No Identity found");
    
           return identities[0];
       
    

(对不起,我无法摆脱这种糟糕的 html 转义)

    然后我根据 IIdentity.Name 从池中获取一个连接,使用此连接从数据库加载用户特定数据并将其存储在我在评估上下文中设置的自定义身份和主体中:

    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    
        IIdentity identity = GetClientIdentity(evaluationContext);
        if (identity == null)
            throw new Exception();
    
            // These are my custom Identity and Principal classes
            Identity customIdentity = new Identity();
            Principal customPrincipal = new Principal(customIdentity);
            // populate identity and principal as required
            evaluationContext.Properties["Principal"] = customPrincipal;
            return true;
        
    

然后我应该可以在需要时使用 System.Threading.Thread.CurrentPrincipal 或 CurrentIdentity 访问我的自定义身份和主体。

希望这在某种程度上有所帮助;我不确定这是不是最好的解决方法,但这是我迄今为止想出的最好的方法......

史蒂夫

【讨论】:

我使用了类似的方法,只是我缓存了用户对象 (IPrincipal) 而不是数据库连接。我尝试将它存储到验证器中的 Thread.CurrentPrincipal 中,但在 AuthorizationPolicy.Evaluate() 调用之前它被 GenericPrincipalin 覆盖。我已经在密码验证器中加载了我的用户对象后调用 AuthorizationPolicy.Evaluate。我认为这是 WCF 设计师的短视。所以基本的想法是我们把它藏在线程的什么地方?为什么我不能在验证器中执行此操作并让 WCF 不理会它? >> Thread.CurrentPrincipal = 用户;

以上是关于WCF 自定义验证器:如何从自定义验证器初始化“用户”对象的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 jwt.io 手动验证从自定义授权服务器获得的令牌?

Rails:从自定义验证器调用标准验证

Grails - 从自定义验证器闭包调用内置约束

从自定义默认 Spring 验证中删除批量

在 WCF/WIF 中,如何合并来自两个不同客户端的自定义 sts 令牌的声明

keycloak 从自定义协议映射器抛出身份验证错误