2010-02-19 12 views
5

Ho un UserNamePasswordValidator personalizzato che chiama nel mio DB Oracle.Convalida personalizzato WCF: come inizializzare un oggetto "Utente" dal validatore personalizzato

Questa classe deriva da System.IdentityModel.Selectors.UserNamePasswordValidator e il metodo Validate() restituisce void.

Carico il mio oggetto Utente dal database e, una volta convalidata la password, voglio nascondere il mio oggetto "Utente" in modo che il servizio possa accedervi quando procede per la sua attività. In ASP.NET/Java land lo metterei in una sessione, o forse la mia classe di controller generale. Come faccio questo dal Validator in WCF?

O, in altre parole, qual è la migliore pratica in terra WCF per impostare un oggetto dominio utente personalizzato per il servizio.

Aggiornamento: Ecco come ho lavorato intorno ad esso. Metto in cache l'oggetto User durante il validatore, quindi lo accedo più tardi nel passaggio 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

Ora sto caricando il mio oggetto Utente (IPrincipal) nel validatore della password, memorizzandolo nella cache in un dizionario statico, acquisendolo in AuthoriziationPolicy. Vedi modifica sopra. È questo il modo migliore? Sicuramente sembra un disastro per una struttura così ricca. – codenheim

+0

Bene, ho rovinato il formato nella mia modifica sopra e non posso ripararlo. Quello doveva essere un metodo completo sopra nell'area del codice. – codenheim

risposta

4

Io non sono un esperto di WCF, ma da quello che ho letto e realizzato finora, il modo 'corretto' per farlo sarebbe quello di utilizzare il Validator per autenticazione l'utente, e quindi implementare un IAuthorizationPolicy per eseguire l'effettiva autorizzazione . Quindi sarebbe nella politica di autorizzazione che imposterai il tuo principal personalizzato sul thread corrente.

Per poter inoltrare informazioni dalla convalida nome utente/password, è possibile implementare un autenticatore di token di sicurezza che eredita da UserNameSecurityTokenAuthenticator. SecurityTokenAuthenticator chiamerà prima il validatore e se la convalida ha esito positivo, potrà aggiungere la propria politica di autorizzazione personalizzata e inviare userinfo alla politica attraverso il costruttore. Qualcosa lungo le linee di questo:

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

C'è un articolo qui che descrive un po 'di più intorno alle classi coinvolte; http://blogs.msdn.com/card/archive/2007/10/04/how-identity-providers-can-show-custom-error-messages-in-cardspace.aspx

+0

Vedrò più da vicino la tua risposta e fare un tentativo, e aggiorno la domanda. Il mettere in cache l'utente (Principal) in una mappa ha risolto il problema immediatamente, e non ho ancora avuto il tempo di rivisitare quella sezione di codice, ma la tua è la prima spiegazione plausibile che ho visto. – codenheim

+0

Come si collega questo nella pipeline? Inoltre, si utilizza CustomUserNameValidator, lo si aggancia anche nel file di configurazione. Se è così, non è chiamato due volte? Questo approccio restituisce una richiesta di autenticazione al client (nel caso sia utilizzata l'autenticazione di base)? –

+0

ERRORE: CustomUsernameSecurityTokenAuthenticator 'non implementa membro astratto ereditato' System.IdentityModel.Selectors.UserNameSecurityTokenAuthenticator.ValidateUserNamePasswordCore (string, string) ' – Nuzzolilo

1

Ho esattamente lo stesso problema.

Sto utilizzando un'API per connettersi al database Oracle sottostante e "convalido" i dettagli di accesso aprendo una connessione.

Desidero quindi archiviare questa connessione da qualche parte (abbastanza facile, creerò un pool di connessioni per tutti i diversi utenti), ma creerò anche un'identità e un principal personalizzati che rappresentano questo utente, così che una volta raggiunta la mia IAuthorizationPolicy personalizzata , non ha bisogno di ricaricare queste informazioni.

Ho fatto un sacco di ricerca e non trovato nulla quindi il mio piano è quello di fare questo:

  1. Convalida password in UserNamePasswordValidator personalizzato con l'apertura di connessione API.

  2. Archivia la connessione aperta nel lotto di connessione con il nome utente.

  3. Quando mia abitudine IAuthorizationPolicy.Evaluate() viene chiamato, guarderò l'identità generica fornita:

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

(dispiace non posso liberarmi di questo povero HTML fuga)

  1. Afferro quindi una connessione dal pool in base a IIdentity.Name, utilizzare questa connessione per caricare dati specifici dell'utente dal database e archiviarli in un'identità e un principal personalizzato che ho impostato nel 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; 
        } 
    

Poi dovrebbe avere accesso al mio un'identità personalizzata e principale ogni volta che ne ho bisogno utilizzando System.Threading.Thread.CurrentPrincipal o CurrentIdentity.

Spero che questo aiuti in qualche modo; Non sono sicuro che sia il modo migliore per farlo, ma è il migliore che ho trovato finora ...

Steve

+0

Ho usato un approccio simile, tranne che ho memorizzato nella cache l'oggetto User (IPrincipal) invece della connessione al database. Ho provato a riporlo a Thread.CurrentPrincipal nel validatore, ma viene sovrascritto con un GenericPrincipalin prima della chiamata AuthorizationPolicy.Evaluate(). AuthorizationPolicy.Valuate viene chiamato dopo che ho già caricato il mio oggetto User nel validatore della password. Penso che questo sia stato miope dai progettisti di WCF. Quindi l'idea di base è dove nel Thread lo nascondiamo? Perché non posso semplicemente farlo nel validatore e lasciare che WCF lo lasci in pace? >> Thread.CurrentPrincipal = utente; – codenheim

Problemi correlati