Cercar en aquest blog

5/12/10

ABC WCF

Com sempre una imatge nova molt interesant.

31/10/10

Distribució del negoci (PART IV)

Bé, en aquest post exposaré com crear un client d' un servei WCF. Fer-ho amb la opció del VS és bastant fàcil. El que faré és exposar com fer una classe genèrica que sigui capaç de generar un client transparent per qualsevol servei, coneixent només les operacions d'aquest (interficie) i els missatges que exposa per interactuar amb ell (contractes de dades).

Primer de tot creerem una llibreria que anomenarem TransparentProxy. I dins d'ella una interfície on definirem les operacions que ha de complir el proxy i una classe que ens implementarà un proxy.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TransparentProxy
{
    interface IProxy<T>
        T Client {get;}
        void Close();
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TransparentProxy
{
    public class Proxy<T>: IProxy<T>
    {
        public T Client
        {
            get { throw new NotImplementedException(); }
        }
        public void Close()
        {
            throw new NotImplementedException();
        }
    }
}

Un cop fet això començarem a implementar el proxy. Per fer-ho em de tenir uns mínims coneixements de WCF.
Bàsicament per conectar-nos a un servei necessitem saber:
  1. La adreça on es troba.
  2. Com és vincualen el client i el servei (binding).
  3. El tipus de seguretat en el transport i el missatge.
  4. I si cal, usuari i clau d'accés. SEMPRE HEM D'UTILTZAR UN MECANISME DE AUTENTIFICACIÓ I AUTORITZACIÓ.
I saber que tot això ho trobem al "assembly" System.ServiceModel .

Realment gran part de tot l'esmentat depén del tipus de vincul que volem implementar, i per tant en una primera versió, es una dada que se li ha de comunicar al proxy.

Així doncs us presento una mínima expressió d'un proxy transparent, molt bàsic, però útil per començar a especialitzar-lo cap els nostres serveis. Jo, per exemple, utilitzo serveis federats amb ADFS 2.0, i m'he creat un proxy transparent capaç de conectar-se a qualsevol dels meus serveis.







using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace TransparentProxy
{
    public class Proxy<T>: IProxy<T>
    {
        ChannelFactory<T> ch;
       
       public Proxy(Binding bind, string addr , string userName, string password )
        {

            ch = new ChannelFactory<T>(bind, new EndpointAddress(addr));
            if ((userName != null) && (password!=null))
            {
                // És un exemple, les credencials poden ser passades
                // d'altres maneres, certificats, tokens, etc.
                // Cal docuementar-se.
                
                ch.Credentials.UserName.UserName = userName;
                ch.Credentials.UserName.Password = password;
            }
        }
        
        public T Client
        {
            get { return ch.CreateChannel(); }
        }


        public void Close()
        {
            ch.Close();
        }
    }
}
 


Exemple d'utilització

WSHttpBinding bind=new WSHttpBinding();
bind.Security.Mode = SecurityMode.Message;
bind.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
bind.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
bind.Security.Message.NegotiateServiceCredential = true;
TransparentProxy.IProxy proxy = new TransparentProxy.Proxy(bind, "http://localhost:53538/WCFGAPService/Service.svc", null, null);
IList res = proxy.Client.LListarMetges();
proxy.Close();
return res;


En el proper post conectarem un client Winforms amb el servei, utilitzant el proxy transparent.


28/10/10

Una Petita intromissió: Gràfic per triar el 'Binding' en un servei WCF

Mirant per internet he trobat aquesta imatge d'allò més interesant.

22/9/10

Distribució del negoci (PART III)

Finalment només ens queda distribuir realment el negoci. Fins ara només tenim llibreries. Necessitem d’alguna tecnologia que ens ajudi a fer arribar als clients les funcionalitats que hem implemetat. Podem pensar-ne moltes però serem practics, la intenció sempre és faclitari no complicar. Podem distribuir-lo amb una aplicació ASP.Net, amb un Web Service o amb un WCF Services. La primera no es la que jo vull, ja que els meus clients seran aplicaions Winforms. La segona està moltbé però hi ha capes que no comtempla, com són diferents tipus de trasnport i seguretat. I per tant distribuirem amb un servei WCF, de manera mot fàcil, Si coneixem la tecnologia, ens podem adonar que donar diferents opcions de comunicació es molt fàcil de fer i treballar amb diferents maneres d’autentificació i autorització també. Així com garantir la seguretat tant a nivell de transport com de missatge. Però tot això són carecterístiques pòpies de WCF independents del negoci que distribueixen. Aqui está la gràcia de arquitecturar els sistemes en capes i nivells.
Primer de tot crerem un nou projecte Web WCF. Un cop fet, agreguerem les referencies a les llibreries que implementen el negoci que volem distribuir. Esborrem totes les classes que tenim a la carpeta App_Code. Editem el “Service.svc” (podem canviar-li el nom si volem), i el modifiquem de la següent manera:





Un cop fet això obriem el Web.config i el modifiquem afegint el nostre negoci com a un servei.




I el provem. Funciona!!!. Ara només li falta afegir configuracions de comportament i seguretat. Però abans de tot jo tinc el costum de provar que funciona i després ja el perfeccionarem. El següent pas serà doncs, crear un client i provar.

10/9/10

Distribució del negoci (PART II)

Un cop tenim definit el servei, pasem a la implementació. Per això afegirem una nova llibreria a la solució que anomenarem ServeisDistribuits.Ingresos.ModulPrincipal.dll.

Afagirem una clase que anomenarem Servei.cs i farem que implementi la interficie IModulPrincipal de la llibreria anteriorment creada. En la implementació de les operacions es on hem de delegar les funcions a la capa de Aplicació on hi ha el negoci o orquestrar (secuenciar) operaciosd’aquesta per poder crear la resposta de la operació del servei en questió. Si ho fem així, aconseguim separar totalment la definició de la implementació. Això pot no tenir sentit en petites aplicacions, però quan la cosa és molt gran podem tenir versions diferents implementades de la mateixa definició. Això facilitat molt la escabilitat, flexibilitat, la reutilització i el desacoplament de capes, i treball en equip, facilitant la posibilitatde fer test abans tenir versions finals, només cal una mica de imaginació.



També hem de pensar que aquesta és l libreria que implementa el servei, i per tant haurà d ser capaç de transmetre els missatges d´error (excepcions) i controlar l’ accés dels usuaris “loginats” en la estació client (en un altre post explicaré com fer-ho demanera molt fàcil)



El codi seria més o menys el seguents.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ServeisDistribuits.Ingresos.ModulPrincipal
{
    public class Servei:IModulPrincipal
    {
        public IList CercarPacient(CriteriCercarPacient criteri)
        {
            // Aqui podem fer una traça personalitzada d'entrada (usuari, PC, Hora, Missatge entrada....)
            
            // Declarem la variable de retorn

            IList llista = new List();
            try
            {
                // Invoquem el negoci (GAP.dll) i 
                // omplim la llista i la retornem Ex: GAP.Search("Pacients",...

                return llista;
            }
            catch (Exception e)
            {
                // Capturem l'error
                // Fem una traça detallada Ex: Tracert(e)
                // i finalment llacem un missatge al client
                throw new FaultException(".... error .....");
            }
        }

        public IList LListarMetges()
        {
             // Aqui podem fer una traça personalitzada d'entrada (usuari, PC, Hora, Missatge entrada....)
            
            // Declarem la variable de retorn

            IList llista = new List();
            try
            {
                // Invoquem el negoci (GAP.dll) i 
                // omplim la llista i la retornem Ex: GAP.Search("Metges",...

                return llista;
            }
            catch (Exception e)
            {
                // Capturem l'error
                // Fem una traça detallada Ex: Tracert(e)
                // i finalment llacem un missatge al client
                throw new FaultException(".... error .....");
            }
        
        }

        public IList LlistarLlits(LlitEstats estat)
        {
            // Aqui podem fer una traça personalitzada d'entrada (usuari, PC, Hora, Missatge entrada....)

            // Declarem la variable de retorn

            IList llista = new List();
            try
            {
                // Invoquem el negoci (GAP.dll) i 
                // omplim la llista i la retornem Ex: GAP.Search("Llits",...

                return llista;
            }
            catch (Exception e)
            {
                // Capturem l'error
                // Fem una traça detallada Ex: Tracert(e)
                // i finalment llacem un missatge al client
                throw new FaultException(".... error .....");
            }
        }

        public ResultatOperacio Ingresar(DadesIngres dades)
        {
            // Aqui podem fer una traça personalitzada d'entrada (usuari, PC, Hora, Missatge entrada....)

            // Declarem la variable de retorn

            ResultatOperacio  resultat = null;
            try
            {
                // Invoquem el negoci (GAP.dll) i 
                // Executem el negoci Ex: GAP.Save(dades
                // i retornem el resultat.

                return resultat;
            }
            catch (Exception e)
            {
                // Capturem l'error
                // Fem una traça detallada Ex: Tracert(e)
                // i finalment llacem un missatge al client
                throw new FaultException(".... error .....");
            }
        }
    }
}

4/9/10

Distribució del negoci (PART I)

Imaginem que tenim desenvolupat tot el negoci de la Gestió Administrativa de Pacients (GAP) en el nostre nou sistema assistencial. Aquesta part del sistema gestiona diferents arees, com poden ser, dades administratives de pacients (adreces, documents identifiatus, relacions parentals, expedients, etc), ingressos, altes, llistes d’espera i citacions. Evidenment, aquestes parts seran gestionades/utilitzades per usuaris/professionals diferents, amb rols diferents, o sigui, amb permisos per fer algunes coses i altres no.
Seguim imaginant, o no, que volem desenvolupar una aplicació que gestioni només la part d’ingresos (el més obvi seria que gestionés ingressos i altes, però serem simplistes). I per tant i seguin amb la meva proposta de disseny, creerem un servei distribuit que encapsuli les operacions (funcions) necessàries per dur a terme aquesta feina.

Seguim sent simplistes, i ens imaginem que tot el negoci el tenil encapsulat en una llibreria que anomenarem GAP.dll. Les funcions que distribuirem sortiran directament d’aqui o orquestrades a partir de funcions d’aqui. Si fem un anàlisi molt simple del que suposa ingressar una persona trobem que hi participen dos actors i un element d’ ubicació:

  1. El metge A ingressa al pacient P1.
  2. On?
  3. Al llit H15B, que no està ocupat.


Per fer això l’usuari haurà de poder realitzar aquesta relació. Per tal de poder fer-ho haurà de poder seleccionar el pacient, el metge i un llit lliure. Un cop fet això fer l’ingrés.
Seguim sent simples. El nostre hospital és molt petit, només té 20 llits i 4 metges. De pacients en tenim més 25. “Per fer una petita demo ja n’hi ha prou.


Un cop fet el petit anàlisi pasem a la implementació (només hem de distribuir una part del negoci implementat).


Primer de tot, creerem una llibreria que anomenarem:
ServeisDistribuits.Ingresos.dll amb el Visual Studio ( jo utilitzo el 2010).
Dins la llibreria creerem una interfície amb totes les operacions. Aquesta interfície defineix el contracte del servei i l’anomenarem IModulPrincipal.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace ServeisDistribuits.Ingresos
{
    [ServiceContract]
    public interface IModulPrincipal
    {
        /// 
        /// Trona una llista de pacients a partir d'un criteri de recerca
        /// 
        /// 
Criteri de recerca/// 
        [OperationContract]
        IList CercarPacient(CriteriCercarPacient criteri);
        /// 
        /// Retorna la llista de pacients actius
        /// 
        /// 
        [OperationContract]
        IList LListarMetges();

        /// 
        /// Retorna un llista de llits que cumpleixen un estat
        /// 
        /// 
/// 
        [OperationContract]
        IList LlistarLlits(LlitEstats estat);
        /// 
        /// Operació (Verb) que fa l'ingrés
        /// 
        /// 
Dades de l'ingrés/// 
        [OperationContract]
        ResultatOperacio Ingresar(DadesIngres dades);


    }
}

Com podem veure les operacions com a màxim tenen un parametre d' entrada (un missatge d'entrada i un missatge de sortida)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
    /// 
    /// Missatge que representa el criteri de recerca de Pacients
    /// 
    [DataContract]
    public class CriteriCercarPacient
    {
        /// 
        /// Part inicial del Cognom1
        /// 
        /// 
        [DataMember]
        public String Cognom1Comenca;
        /// 
        /// Part Inicial del cognom2
        /// 
        /// 
        [DataMember]
        public String Cognom2Comenca;
        /// 
        /// Part inicial del Nom
        /// 
        /// 
        [DataMember]
        public String NomComenca;

    }
}

--------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
    public class DadesIngres
    {
        [DataMember]
        public String IdPacient;
        [DataMember]
        public String IdMetge;
        [DataMember]
        public String IdLlit;
        [DataMember]
        public String Observacions;
    }
}
---------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
    [DataContract]
    public class InformacioLlits
    {
        [DataMember]
        public String Id;
        [DataMember]
        public EstatLlit Estat;

    }

    public enum EstatLlit
    {
        Buit=1,Ocupat=2
    }
}

---------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
    [DataContract]
    public class InformacioMetges
    {
        [DataMember]
        public String Id;
        [DataMember]
        public String NomMetge;

   
    }
}
---------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
    [DataContract]
    public class InformacioPacient
    {
        [DataMember]
        public String Id;
        [DataMember]
        public String NomPacient;
    }
}
---------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ServeisDistribuits.Ingresos
{
    public enum LlitEstats
    {
        Tots=0, Buits=1, Ocupats=2
    }
}
---------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ServeisDistribuits.Ingresos
{
   [DataContract]
    public class ResultatOperacio
    {
       [DataMember] public String Missatge;
    }
}


.... continuarà

15/7/10

Disseny: Una aproximació a la capa de negoci

Un cop fet l’ anàlisi funcional de qualsevol aplicació cal fer el disseny d’aquesta de manera que s’inclogui o encaixi dins l’arquitectura del conjunt d’aplicacions que formen l’entramat de sistemes d’ informació de l’empresa.

Per tant, previament hem de disposar d’una arquitectura que ens permeti encaixar qualsevol aplicació nova que volem desenvolupar.

Al meu parer, aquesta arquitectura ha de donar la posibilitat de que la seva implementació ha de ser independent de la seva distribució, i per tant de com s’accedeix a ella per part de l’usuari en podem parlar més endavant. Per mi una aplicació dins el món dels serveis no és res més que un contracte de funcionalitats (interface) i la implementació d’aquestes.

Quan implementem, podem veure que definim entitats noves i pot ser que en reutilitzem algunes altres.

“Les entitats són classes persistents, on dues del mateix tipus es diferencien per un identificador únic”

D’aquestes obtenim dos requisits indispensables dins el nostre sistema. Una és la operativa,, amb aquestes entitats hem de fer alguna cosa, i l’altre és l’enmagatzament de la seva informació, persitència de les entitats. Pel que fa la part operativa, definirem un contracte amb totes les operacions que podem dur a terme amb una entitat i els seus agregats (entitats dèbils). I pel que fa a la persitència, cal buscar una arquitectura que ens permeti fer operacions de persitència independenment de lloc (BD, Fitxer, servei, etc) on s’acabi enmagatzeman. El patró de disseny triat és el “Repository Pattern”.

Un cop resumit més o menys una part del disseny, la part que englobaria tota la capa de negoci, especifiquem-ho una mica més.

Per mi la capa de negoci d’un sistema empresarial ha de ser distribuible, de diferents maneres, flexible i productiva (fàcilment escalable). Tampoc ha de ser cap problema l’augment de nivells (servidors) on aquesta es troba allotjada.

CAPA DE NEGOCI



Bé amb la imatge anterior podem veure com veig jo la capa de negoci d’un sistema empresarial.

Capa Aplicació: Aqui es defineixen el contractes de les diferents aplicacions i s’implementen, Per exemple si tenim una aplicació que gestiona l’administració de pacients, una altre que gestiona l’assistència de les hospitalitzacions i una altre la de les urgències, en aquesta capa definiriem els contractes de les operacions o funcions extretes dels anàlisis funcionals duts a terme pels analístes. També implementariem aquests contractes. La implementació de les funcionas es basarà en orquestrar serveis de la capa de domini.

Capa de domini: La més important. El patró escollit és el “DDD-Domain Driven Design”. Crec que és una bona opció. En aquesta capa, seguint el patró decrit, es defineixen les entitats, es defineixen i s’implementen els contractes d’operaions de cada entitat i els seus agregats, i es defineixen les operacions del repositoris de cada entitat, però no s’implementen.

Capa de Infrastructura: Aqui és on implementem els repositoris de cada entitat. Aquest són els que acaben fent operacions de persistència a la BD, fitxer , Servei.

Evidenment això no és tot, cal practicar a fons la POO en la implementació, abstracció, herència, especilització, generalitació, etc. D’aquesta pràctica en sortiran els “cores” de cada capa.









20/5/10

Diseny SOLID, el bon disseny

El dia 13 de Maig d'enguany (2010), es va celebrar a Barcelona el XXV 'Foro' d'arquitectura de Microsoft, promogut per Microsoft Ibérica. Bàsicament es va centrar en les novetats del Framework 4.0 i Visual Studio 2010, extenen-se en demostracions basades el l'aplicatiu desenvolupat com a 'demo' d'una arquitectura orientada al domini. Si entrem a la Web d'arquitectura http://msdn.microsoft.com/es-es/architecture/default.aspx podem accedir als continguts i el codi.

Tot el que és va dir va ser molt interesant, jo no hi vaig assitir però en tinc bons 'inputs' i m'he llegit les presentacions. De totes les lectures, i sense desmereixen cap, he trobat mol interesant la que parla de desacoplament entre capes de la arquitectura, presentada per Hadi Hariri, i els principis del disseny SOLID.

El dissney SOLID diu bàsicament cinc coses a tenir en compte, si volem un resultat en la implementació, sòlid, robust, escalable i flexible.

Single Responsibility Principle: Una classe ha de ser dissenyada i implementada per tenir una sola responsabilitat. Si una classe és la responsable d'autenticar usuaris, només té la responsabilitat d'autenticar usuaris.

Open Closed Principle: Una classe ha d'estar oberta a extensions, però mai a canvis. Per entendre-ho, a una classe li podem afegir nous metodes, podem especificar-la més (herència), però mai canviar la seva signatura pública, propietats, metodes i funcions, inicials.

Liskov Substitution Principle: Si tenim una classe subtipus d'una altre, en l¡ aplicació les classes pare han de poder ser substituides per les classes fill sense cap problema, i alteracions de l'aplicació.

Interface Segregation Principle: Mai s'ha d'obligar als clients a implementar interfaces que no requereixen.

Dependency Inversion Principle: Intentar aplicar desacoplament entre capes diferents de la arquitectura.


En el següent enllaç podeu trobar totes les presentacions en format pdf.


27/4/10

La capa de dades: Entity Framework

Per fer una petita introducció, recomano la lectura de les següents entrades de la llinreria MSDN.



Un exemple aclaridor de la potencia d'aquest framework la podem veure en una de les seccions a la que ens guia el segon enllaç, "Walkthrough: Serialize Self-Tracking Entities (Entity Framework)" o "Attaching and Detaching Objects (Entity Framework)" o "Working with Self-Tracking Entities (Entity Framework)".

I també estaria bé fer una lectura del que no es recomana a l'hora de fer aplicacions de n-capes, "Anti-Patterns To Avoid In N-Tier Applications", i el que si està bé "N-Tier Application Patterns"







Aprenem més...........!!!

22/4/10

Proveïdor d’Identitats: Un exemple per entendre-ho tot (VI)

(PART V)

Bé doncs, ara implementarem la segona part, entrar des del servei fronterer al servei final, i que aquest rebi les credencials de l’usuari que ha iniciat el procés en l’estació client.


Primer de tot, editarem el fitxer de configuració del servei fronterer i afegirem el següent:

 
Després agreguem una referència al servei final.
 
 
I, tornant al web.config del servei fronterer apliquem la següent modificació:
 
Tot seguit modifiquem la implementació d’ambdós serveis, per tal d’aplicar la funcionalitat que volem demostrar.





En el servei fronterer (Front_End_Service):

public string GetDataUserName()
        {
            //TODO: Change the code below to handle your claims usage.
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;

            SecurityToken st = identity.BootstrapToken;
            if (st == null)
            {
                st = principal.Identities[0].BootstrapToken;
            }

            string _sconf = "WS2007FederationHttpBinding_IService";
            RequestSecurityTokenResponse _rsts = new RequestSecurityTokenResponse();

            STSRPClient c2 = new STSRPClient(st, _sconf);

            Back_End_Service.IServiceChannel cl2 = c2.ClientActAs;

            string res1 = cl2.GetDataUserName();

            cl2.Close();

            return string.Format("Front_End_Service: tu ets {0}{1}{2}" , identity.Name, "\r\n" ,res1);
        }


I en el servei final (Back_End_Service):
public string GetDataUserName()
        {
            //TODO: Change the code below to handle your claims usage.
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;

            return string.Format("Back_End_Service: tu ets {0} i l'actor és {1}", identity.Name, identity.Actor.Name);
        }


I no oblidem importar les llibreries IDP i STS al servei fronterer.


Ara només queda accedir a la consola del ADFS 2.0 i configurar el servei final per tal de que accepti delegació. Per fer-ho, haurem de donar autorització per delegar a l’usuari del pool de connexions on es trobi el servei fronterer dins el servidor d’aplicacions (IIS+WAS). Normalment el servei de xarxa, tot i que no és el més convenient, si volem fer crides des de servidors diferents. És millor utilitzar un usuari del domini, amb permisos d’accés (els necessaris) al AD.
Primer de tot, dins la consola, editem les reclamacions del servei final:

Tot seguit ens posicionem en la pestanya de regles d’autorització de delegació.




Afegim la regla que autoritzarà a l'usuari del pool d'aplicacions del servei fronterer a l' IIS a actuar com a l'usuari que hi ha encaixat en el token que s'ha generat des de la estació client per accedir al servei fronterer i que també ens servirà per accedir al servei final. Això és el que s'anomena "delegar el control l'accés".


L'usuari del servei de xarxa, és el que i ha configurat al pool del servei fronterer a l'IIS. Quan un servei o aplicació té configurat aquest usuari en el seu pool, i accedeix a un recurs de xarxa, l'usuari es converteix dins el domini en l'usuari [domini\nomdehost$], molt important saber-ho. Una altre cosa que hem de fer, es donar permisos d'accés a la clau privada del certificat que utilitzem per signar els tokens a aquest usuari.

Tot seguit, un cop afegida la regla, ens posicionem a les regles de transformació (primera pestanya). Esborrem tot el que hi hagi, i hi afegim dues regles que deixarem passar “reclamacions” del token d’entrada, el que utilitzem per accedir al servei de forma delegada.






Fem el mateix amb el Rol, i finalment tenim:




I ja està, ara només cal provar un altre cop tot el procés, provem i obtenim:



Finalment podem veure com fem traspassar de manera transparent, les credencials de l’usuari fins al servei final. Com que no podem fer un login de l’usuari en el servei fronterer (no podem, desconeixem la contrasenya de l’usuari) utilitzem autoritzacions de delegació per tal d’aconseguir-ho.



Amb aquest senzill exemple, es demostra una de les grans utilitats que pot oferir un gestor d’identitats.

Proveïdor d’Identitats: Un exemple per entendre-ho tot (V)


(PART IV)
Per implementar la negociacio amb el STS i el accés al servei, primer ens crearem un a llibreria en C# i anomenada IDP i li posarem aquesta classe dins: 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using Microsoft.IdentityModel.Protocols.WSTrust;
using System.ServiceModel.Security;

namespace IDP
{
    public static class EndPoints
    {
        public static string baseUri = "http://[idphost]/adfs/services/";
        public static string SSLbaseUri = "https://[idphost]/adfs/services/";
        
        public  static SecurityToken GetTokenFrom_trust_13_usernamemixed(string username, string password, string appliesTo, out RequestSecurityTokenResponse rsts)
        {
            string adrecaSTS = "trust/13/usernamemixed";

            WS2007HttpBinding binding = new WS2007HttpBinding();

            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Mode = SecurityMode.TransportWithMessageCredential; //https




            WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(SSLbaseUri + adrecaSTS));
            trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
            trustChannelFactory.Credentials.UserName.UserName = username;
            trustChannelFactory.Credentials.UserName.Password = password;
            trustChannelFactory.ConfigureChannelFactory();

            WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();


            //create a token issuance issuance
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
            //Relying Party’s identifier
            rst.AppliesTo = new EndpointAddress(appliesTo);
            //call ADFS STS
            SecurityToken token = tokenClient.Issue(rst, out rsts);

            return token;
        }
        
        public static SecurityToken GetTokenFrom_trust_13_windows(string appliesTo, out RequestSecurityTokenResponse rsts)
        {
            string adrecaSTS = "trust/13/windows";

            WS2007HttpBinding binding = new WS2007HttpBinding();

            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.NegotiateServiceCredential = true;




            WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(baseUri + adrecaSTS));
            trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
            trustChannelFactory.ConfigureChannelFactory();

            WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();


            //create a token issuance issuance
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
            //Relying Party’s identifier
            rst.AppliesTo = new EndpointAddress(appliesTo);
            //call ADFS STS
            SecurityToken token = tokenClient.Issue(rst, out rsts);

            return token;
        }

  
    }
}


Aquesta classe ens ajuda a obtenir tokens del STS per un servei en concret. El primer mètode, a partir d’unes credencials entrades per l’usuari, i el segon utilitzan les credencials del usuari loginat al SO (usuari Windows).


I ara, per tal de facilitar la creació de clients de Serveis (Agents de servei), que puguin actuar amb o sense delegació, utilitzarem una altre llibreria que ens facilitarà la feina. Aquesta la farem en VB.Net i li direm STS. Dins hi posarem la següent classe. Hem de fer les referències.

Imports System.IdentityModel.Tokens
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports Microsoft.IdentityModel.Protocols.WSTrust
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Security
Imports System.ServiceModel.Security.Tokens
Imports System.Text

Public Class STSRPClient(Of T)
    Implements IDisposable
#Region "Members"
    Private _st As SecurityToken
    Private _factory As ChannelFactory(Of T)
#End Region
    ''' 
    ''' Contructor per generar Client a partir del fitxer de configuració
    ''' 
    ''' 
''' 
''' 
    Sub New(ByVal st As SecurityToken, ByVal bindingConfiguration As String)
        Create(st, bindingConfiguration)
    End Sub
  
    Private Sub Create(ByVal st As SecurityToken, ByVal bindingconfiguration As String)
        Me._st = st
        _factory = New ChannelFactory(Of T)(bindingconfiguration)
        _factory.ConfigureChannelFactory()
    End Sub

    Public Sub Close()
        _factory.Close()
    End Sub

    Public ReadOnly Property Client As T
        Get
            Return _factory.CreateChannelWithIssuedToken(_st)
        End Get
    End Property
    Public ReadOnly Property ClientActAs As T
        Get
            Return _factory.CreateChannelActingAs(_st)
        End Get
    End Property

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If
            If Me._factory.State <> CommunicationState.Closed Then
                _factory.Close()
            End If
            _st = Nothing
            ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
    'Protected Overrides Sub Finalize()
    '    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class


Un cop compilades, farem les referències a aquestes en l’aplicació client.


  1. Obtenir token per l’usuari
  2. Crear client amb el token
  3. Invocar Servei
  4. Escriure el resultat

Imports System.IdentityModel.Tokens
Imports Microsoft.IdentityModel.Protocols.WSTrust

Public Class Form1

    Private Sub BInvoke_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BInvoke.Click

        Dim adrecaservei As String = "http://localhost/Front_End_Service/Service.svc"

        ' App.config - Nom de la configuració del binding del client
        Dim conf As String = "WS2007FederationHttpBinding_IService"


        ' Primer hem d'obtenir un token de seguretat del IDP pel servei

        Dim rsts As New RequestSecurityTokenResponse
        Dim st As SecurityToken = IDP.EndPoints.GetTokenFrom_trust_13_usernamemixed(Me.TxtUser.Text, Me.TxtPwd.Text, adrecaservei, rsts)

        ' Un cop el tenim em de fer-lo servir per accedir-hi i invocar les seves operacions

        Dim clirp As New STS.STSRPClient(Of Front_End_Service.IServiceChannel)(st, conf)
        Dim client As Front_End_Service.IServiceChannel = clirp.Client

        Dim response As String = client.GetDataUserName()

        clirp.Close()
        clirp.Dispose()


        Me.TextBox1.AppendText(response + vbNewLine)

    End Sub
End Class



Diagrama de seqüències del event del botó.

Ara ja podem compilar i provar. De moment només invoquem el primer servei.



Invocació amb unes credencials incorrectes

 
Invocació amb credencials correctes.
 

Proveïdor d’Identitats: Un exemple per entendre-ho tot (IV)

(PART III)

Obrim l ‘administrador del IIS. Podem veure els serveis del ADFS i els nostres serveis.


Primer de tot, afegim un pool nou de connexions, que funcioni amb el .NET Framework 4.0. No és necessari però evitarem problemes (que no explicaré perquè depenen de la combinació de versions de SO + IIS). Aquest nou pool és el que utilitzaran els nostres nous serveis.


Bé ara ja podem tornar al Visual Studio, i picar una mica de codi.



Primer de tot podem mirar els web.config dels serveis i esbrinar que vol dir tot el que ha posat el procés de federació. És una bona pràctica.

Un cop assabentats d’aquesta nova tecnologia, modificarem els serveis.



Primer modificarem la Interfície d ‘ambdós, perquè quedi de la següent manera:
....

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string GetDataUserName();
    }

....


Tot seguit implementem la interfície en ambdós serveis.

Front_End_Service
using System.Threading;
using Microsoft.IdentityModel.Claims;
namespace Front_End_Service
{
    public class Service : IService
    {
         public string GetDataUserName()
        {
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;
            return string.Format("Front_End_Service: tu ets {0}", identity.Name);
        }
    }
}



Back_End_Service

using System.Threading;
using Microsoft.IdentityModel.Claims;
namespace Back_End_Service
{
    public class Service : IService
    {
        public string GetDataUserName()
        {
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;
            return string.Format("Back_End_Service: tu ets {0} i l'actor és {1}", identity.Name, identity.Actor.Name);
        }
    }
}



Després ens situarem a l’ aplicació client i crearem una referència al servei “Front_End_Service”.


Un cop fet això modificarem el fitxer “app.config”, per tal de posar el endpoint del STS que consumirà el token de seguretat.



Cercarem el “tag” següent :

 
 
I el substituirem per aquest, el trobarem comentat en el mateix app.config:
 
 
Podríem utilitzar-ne un altre, però aquest, és una bona opció. Una bona pràctica seria entendre els endpoints del STS.

Tot seguit crearem un formulari semblant a aquest, amb unes entrades de usuari i contrasenya , un botó per invocar el servei, i una consola per mostrar el resultat.

I, implementarem el codi que farà possible la invocació del servei. Sabem que necessitem un token de seguretat per accedir al servei, i el token ens l’ha de donar el STS a través del ADFS 2.0.
 

Proveïdor d’Identitats: Un exemple per entendre-ho tot (III)

(PART II)

Tot seguit anirem a la consola de gestió del ADFS 2.0 i configurarem l’autentificació del servei “Front_End_Service” el fronterer, el que dona accés al client a la capa de negoci.




Un cop entrem hem de seguir uns passos molt senzills.




Pot ser que en aquest punt tinguem problemes de “certificats”. Si es així triem la segona opció, i anem a buscar de meta dades de federació al directori del projecte.


Li posem un nom.


Li donem permís a tots el usuaris. Això vol dir que qualsevol usuari de l’ Active Directori podrà accedir al servei un cop s’hagi validat.




En el següent pas entrarem les regles d’autorització, i configurarem els “claims” o reclamacions o atributs o propietats, digueu-li com voleu, que s’enviaran des del IDP al RP, o sigui des del servei STS al RP STS, que són els nostres serveis.




Aquí li diem que envií la propietat de l’usuari SAM-Account-Name com a un “claim” de tipus Name, i els grups als que pertany com a “claims” de tipus Role.


Aquí li diem que envií la propietat de l’usuari SAM-Account-Name com a un “claim” de tipus Name, i els grups als que pertany com a “claims” de tipus Role.




Finalment tenim les regles dels claims que enviarem d'un usuari autenticat i autoritzat. En la pestanya del mig tenim les regles d'autoritzacio d'accés al servei.

Aquest procés l’hem de fer amb ambdós serveis.



Proveïdor d’Identitats: Un exemple per entendre-ho tot (II)

(PART I)

Tot seguit el que cal es federar els serveis amb el nostre IDP (identity Provider), que en el nostre cas és ADFS 2.0 RC.

 
Tot seguit la seqüència de federació del servei.
 

El “STS WS-Federation metadata document location” normalment està al servidor on hem instal•lat el ADFS 2.0:
https://[idphost]/FederationMetadata/2007-06/FederationMetadata.xml


Podem xifrar o no el token de seguretat. Si ho fem podem generar un certificat o triar-ne un existent .
Tot seguit veiem tots els tipus de “claims” que ofereix el Security Token Service (STS) a través del IDP.


Finalment, podem programar una tasca que ajusta la configuració de federació si es produeixen canvis en el IDP.



Aquest procés de federació l’hem de fer per ambdós serveis.

Proveïdor d’Identitats: Un exemple per entendre-ho tot (I)

Per fer referència a tota una arquitectura de gestió d’identitats que fan servir aplicacions actives (winforms, windows presentation foundation, windows communication services, workflow services, aplicacions RIA, windows mobile, etc) i aplicacions passives (ASP.Net, aplicacions Web en general), unificada, centralitzada i basada en estàndards, que millor que presentar un escenari i fer-ne el desenvolupament.

Com que d’exemples d’aplicacions passives n’està ple a Internet, ens centrarem en un escenari actiu.

Descripció Escenari: Suposem un domini d’aplicació format per una aplicació client desenvolupada en winforms, que accedeix a un servei distribuït (WCF), i aquest a un altre servei distribuït (WCF). Fins ara cap cosa nova. Però ara hi posarem unes petites regles:
  • L’ usuari s’haurà d’autenticar per accedir a l’aplicació winforms, al servei fronterer i al darrer. Amb un cop n’hi hauria d’haver prou, no?
  • Ha de suportar que els serveis definits estiguin en nivells (servidors) diferents, inclòs en xarxes diferents i dominis diferents (en l’exemple no ho farem per falta de recursos, però és del tot possible i fàcil de fer).
  • La transmissió de credencials entre aplicacions actives ha de ser segura.
  • Els components de seguretat que fan referència a l’autentificació, autorització i transmissió de credencials entre aplicacions han de ser transparents pel desenvolupador. Afegir els components ha de ser un automatisme de posada en producció dins un entorn empresarial, seguin les normes establertes.
Per poder desenvolupar l’ escenari, disposarem d’una serie de recursos:
  • Dos servidors amb Windows 2008 R2, IIS + WAS, Windows Identity Foundation (WIF). En un dels dos hi instal•larem AD+ADFS 2.0 RC (hi ha molta documentació per Internet). Frameworks fins al 3.5.1. Podem instal.lar el 4.0 però a hores d’ara no és una versió publicada.
  • Una estació client amb Windows 7 o XP, amb els frameworks i WIF mencionats.
  • Una eina de desenvolupament com pot ser VS2008 o VS2010 Beta o RC, instal.lada en una de les màquines anteriorment citades. (Jo personalment, instal•lo un Visual Studio a cada servidor de desenvolupament).


Si ens fixem bé en les dependències, els serveis depenen directament de la capa de seguretat per funcionar, i per tant les aplicacions client indirectament també necessiten d’aquesta per utilitzar-los.

I per tant la seqüència que haurà de seguir l’ aplicació client per accedir al servei frontera serà:

  1. Demanar usuari i contrasenya.
  2. Connectar amb la capa de seguretat, presentar les credencials d’usuari i sol•licitar un token per accedir al servei. Si està autoritzat, aquesta li servirà un token amb la informació que necessita de l’usuari el servei per funcionar, per ser operatiu.
  3. L’agent del servei (client del servei, Proxy del servei) presentarà el token a aquest, per tal de que doni accés a la invocació de les seves operacions.
  4. El servei veurà la identitat de l’usuari que està executant l’aplicació client. Si volem transmetre-la cap al servei final, em de tenir en compte algunes coses. Primer de tot, l’usuari que executa el procés del servei al servidor, és el que hi hagi definit en el pool del IIS on estigui allotjat el servei (Normalment NetWorkService). Per tant, hem de guardar el token que arriba sense desxifrar, per poder-lo utilitzar per invocar al servei final, ja que aquest porta les credencials del usuari tal i com les entén la capa de seguretat. I a l’agent del servei final li hem de dir que encara que l’usuari executor del procés sigui el NetWorkService, actuí com a l’usuari que ha iniciat a l’aplicació client. Aquesta sol•licitud de delegació la gestiona la capa de seguretat i aquesta l’ha de permetre.
  5. Finalment, al servei final hem de tenir dues identitats. Un Actor que serà el NetWorkService i un usuari principal que serà l’usuari que ha iniciat l’aplicació client. O sigui el NetWorkService actua com a l’usuari de l’aplicació client en el servei final, el qual pot està allotjat a la Xina. Transmetem una tarja d’identitat des de l’ inici fins al final.
Anem per feina: Implementació
Primer de tot creem una solució al Visual Studio (jo utilitzo el 2010). Si hem instal•lat bé el WIF SDK, tindrem uns templates i un Add-in que ens facilitaran la feina. Alhora de crear WCF Serveis preparats per treballar amb identitats i per posar-los en producció amb un gestor d’identitats (IDP) com pot ser ADFS 2.0.
Si ens hi fixem els serveis els ubiquem al IIS local. La solució final ha de tenir una aparença semblant a la de la imatge següent.


Dos serveis habilitats per treballar amb “claims” i una aplicació Winforms.