2010-02-19 10 views
5

J'ai un UserNamePasswordValidator personnalisé qui appelle dans ma base de données Oracle.Validateur personnalisé WCF: Comment initialiser un objet "Utilisateur" à partir du validateur personnalisé

Cette classe dérive de System.IdentityModel.Selectors.UserNamePasswordValidator et la méthode Validate() renvoie void.

Je charge mon objet Utilisateur à partir de la base de données, et une fois le mot de passe validé, je veux stocker mon objet "Utilisateur" afin que le service puisse y accéder pour ses activités. Dans le domaine ASP.NET/Java, je le planquerais dans une session, ou peut-être dans ma classe générale de contrôleur. Comment puis-je faire cela à partir du Validateur en WCF? Ou, en d'autres termes, quelle est la meilleure pratique dans le terrain WCF pour définir un objet de domaine utilisateur personnalisé pour le service.

Mise à jour: Voici comment j'ai travaillé autour. Je cache l'objet Utilisateur pendant le validateur, puis j'accède plus tard à l'étape 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; 
    } 
+0

Je charge maintenant mon objet Utilisateur (IPrincipal) dans le validateur de mot de passe, en le mettant en cache dans un dictionnaire statique, en le saisissant dans la AuthoriziationPolicy. Voir modifier ci-dessus. Est-ce la meilleure façon? Sûr semble être un kludge pour un cadre si riche. – codenheim

+0

Eh bien, j'ai foiré le format dans mon édition ci-dessus et je ne peux pas le réparer. C'était censé être une méthode complète ci-dessus dans la zone de code. – codenheim

Répondre

4

Je ne suis pas un expert WCF, mais de ce que je l'ai lu et mis en œuvre jusqu'à présent, la façon de le faire « correct » serait d'utiliser le Validator pour authentifier l'utilisateur, puis implémentez un IAuthorizationPolicy pour effectuer l'autorisation réelle. C'est donc dans la politique d'autorisation que vous allez définir votre principal personnalisé sur le thread actuel. Pour pouvoir transférer des informations à partir de la validation du nom d'utilisateur/du mot de passe, vous pouvez implémenter un authentificateur de jeton de sécurité héritant de UserNameSecurityTokenAuthenticator. SecurityTokenAuthenticator appelle d'abord le validateur et si la validation réussit, il peut ajouter votre stratégie d'autorisation personnalisée et envoyer l'info-utilisateur à la stratégie via le constructeur. Quelque chose un long des lignes de celle-ci:

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

Il y a un article qui décrit ici un peu plus autour des classes concernées; http://blogs.msdn.com/card/archive/2007/10/04/how-identity-providers-can-show-custom-error-messages-in-cardspace.aspx

+0

Je vais regarder de plus près votre réponse et essayer, et mettre à jour la question. La mise en cache de l'utilisateur (Principal) dans une carte a résolu le problème immédiatement, et je n'ai pas encore eu le temps de revoir cette section du code, mais la vôtre est la première explication plausible que j'ai vue. – codenheim

+0

Comment accroches-tu cela dans le pipeline? En outre, vous utilisez CustomUserNameValidator, vous l'accrochez également dans le fichier de configuration. Si oui, n'est-ce pas appelé deux fois? Cette approche renvoie-t-elle une demande d'authentification au client (dans le cas où Basic Auth est utilisé)? –

+0

ERREUR: CustomUsernameSecurityTokenAuthenticator 'n'implémente pas le membre abstrait hérité' System.IdentityModel.Selectors.UserNameSecurityTokenAuthenticator.ValidateUserNamePasswordCore (string, string) ' – Nuzzolilo

1

J'ai exactement le même problème. J'utilise une API pour me connecter à ma base de données Oracle sous-jacente, et je "valide" les détails de connexion en ouvrant une connexion. Je veux ensuite stocker cette connexion quelque part (assez facile, je vais créer un pool de connexion pour tous les utilisateurs), mais aussi créer une identité personnalisée et principal représentant cet utilisateur, de sorte qu'une fois qu'il arrive à mon IAuthorizationPolicy personnalisé , il n'a pas besoin de recharger cette information.

Je l'ai fait beaucoup de recherche et rien trouvé si mon plan est de le faire:

  1. détails Valider de connexion à UserNamePasswordValidator personnalisée en ouvrant la connexion API.

  2. Stocker la connexion ouverte dans le pool de connexions sous le nom d'utilisateur.

  3. Quand est appelé mon habitude IAuthorizationPolicy.Evaluate(), je vais regarder l'identité générique fourni:

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

(désolé, je ne peux pas me débarrasser de ce pauvre HTML échapper)

  1. Ensuite, je prends une connexion à partir du pool basé sur le IIdentity.Name, utiliser cette connexion pour charger des données spécifiques à l'utilisateur à partir de la base de données et stocker dans une identité personnalisée et Principal que j'ai mis dans l'Evaluat ionContext:

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

alors je devrais avoir accès à mon identité personnalisée et principale chaque fois que je besoin en utilisant System.Threading.Thread.CurrentPrincipal ou CurrentIdentity.

Espérons que cela aide d'une certaine façon; Je ne suis pas sûr que ce soit la meilleure façon de s'y prendre, mais c'est le meilleur que j'ai trouvé jusqu'ici ...

Steve

+0

J'ai utilisé une approche similaire sauf que je met en cache l'objet Utilisateur (IPrincipal) au lieu de la connexion à la base de données. J'ai essayé de le mettre à Thread.CurrentPrincipal dans le validateur, mais il est écrasé avec un GenericPrincipalin avant l'appel AuthorizationPolicy.Evaluate(). AuthorizationPolicy.Evaluate est appelée après que j'ai déjà chargé mon objet Utilisateur dans le validateur de mot de passe. Je pense que c'était une vision à court terme de la part de WCF. Donc, l'idée de base est où dans le fil est-ce que nous le cachons? Pourquoi ne puis-je pas le faire dans le validateur et laisser la WCF le laisser tranquille? >> Thread.CurrentPrincipal = utilisateur; – codenheim