Windows Identity Foundation è un argomento complesso ma rappresenta anche l'unico modo possibile di gestire il processo di autorizzazioni in un numero sempre maggiore di casi. Per esempio in reti non-solo-Windows, in scenari di tipo Cloud o più semplicemente quando i ruoli vanno stretti.
Un altro buon motivo per evitare come la peste la security di WCF basata su ServiceSecurityContext è che è in obsolescenza. Chi era alla mia sessione ricorderà che ho sottolineato quanto i Claim di System.IdentityModel.Claims siano totalmente differenti e non compatibili con quelli di WIF che si trovano in Microsoft.IdentityModel.Claims.
Invece di ipotizzare scenari esoterici basati sulla Cloud, mi piacerebbe poter convincere gli adepti di WCF ad usare i Claim anche nel più banale dei servizi.
Partirò quindi da un progetto WCF basato su Asp.net (creato dal wizard "WCF Service Application" di Visual Studio) con il ben noto servizio Service1.
Solitamente chi sviluppa nella propria LAN vuole evitare di giocare con i certificati digitali perciò la prima modifica è di abilitare la Windows Authentication su questo servizio. Attenzione però a non utilizzare questo tipo di autenticazione su Internet. Le conseguenze le ho mostrate più volte nelle sessioni di hacking.
Con la Windows Authentication i gruppi sono mappati sui ruoli e quindi possiamo avvalerci di PrincipalPermissionAttribute e di IsInRole ma solo se si dice a WCF di utilizzare "UseAspNetRoles" nell'elemento serviceAuthorization nella configurazione dei behavior, cosa che non mi piace perché lega la strategia di autenticazione all'host (IIS).
Per abilitare Windows Authentication:
- Eseguo Visual Studio come administrator e dalle proprietà del progetto faccio creare la Virtual Directory. In alternativa si può utilizzare IIS Express
- Abilito sia la autenticazione anonima che quella Windows
- All'interno del web.config, nella sezione <system.web> aggiungo:
<authentication mode="Windows"/>
<authorization>
<deny users="?"/>
</authorization>
- Configuro basicHttpBinding in modo che tutti i servizi http del progetto facciano uso di http e Windows Authentication:
<bindings>
<basicHttpBinding>
<binding >
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
Quest'ultimo punto è specifico di WCF4. In alternativa è possibile utilizzare queste impostazioni per configurare uno specifico servizio con questa configurazione di binding.
Per fare un veloce test, modifichiamo la GetData del servizio in questo modo:
public class Service1 : IService1
{
public string GetData(int value)
{
return GetAuthInfo();
}
private string GetAuthInfo()
{
var securityContext = ServiceSecurityContext.Current;
var principal = System.Threading.Thread.CurrentPrincipal as IClaimsPrincipal;
var identity = principal == null ? null : principal.Identity as IClaimsIdentity;
StringBuilder sb = new StringBuilder();
if (securityContext != null)
sb.AppendLine("ServiceSecurityContext enabled: " + securityContext.WindowsIdentity.Name);
if (identity != null)
sb.AppendLine("Claims enabled: " + identity.Name);
return sb.ToString();
}
Naturalmente IClaimsPrincipal e IClaimsIdentity sono presenti in Microsoft.IdentityModel che si trova nel WIF sdk. Se la configurazione è corretta il risultato sarà "ServiceSecurityContext enabled: …".
A questo punto siamo pronti per abbandonare i ruoli e saltare nel mondo dei Claim.
Si può fare tutto via configurazione grazie a "Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement" ma questo implica di esplicitare la configurazione del servizio e perdere i vantaggi della configurazione semplificata di WCF4.
Per questo motivo ripiego su una soluzione molto semplice, vale a dire un Service Behavior applicato al servizio come attributo:
public class FederatedServiceCredentialsAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
Microsoft.IdentityModel.Tokens.FederatedServiceCredentials.ConfigureServiceHost(serviceHostBase, serviceDescription.ServiceType.FullName);
}
}
Il Behavior che ho scritto fa le stesse cose di ConfigureServiceHostBehaviorExtensionElement ma si applica come attributo all'implementazione del servizio:
[FederatedServiceCredentials]
public class Service1 : IService1
{
Infine è necessario qualificare il servizio presso WIF modificando il web.config:
<configuration>
<configSections>
<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<microsoft.identityModel>
<service name="WCFClaimBasedWithoutSTS.Service1"/>
</microsoft.identityModel>
...
Fatto questo siamo finalmente "Claims enabled" e possiamo utilizzare IClaimsPrincipal e IClaimsIdentity.
Vantaggi principali:
- possiamo avvalerci di PrincipalPermissionAttribute, di IsInRole ma anche di ClaimsPrincipalPermssionAttribute
- possiamo aggiungere/modificare Claim scrivendo un nostro semplicissimo ClaimsAuthenticationManager
- possiamo centralizzare l'autorizzazione scrivendo un ClaimsAutorizationManager
I Claim non sono booleani come i ruoli ma possono contenere valori arbitrari e questo ci fornisce molta più potenza nel controllo delle autorizzazioni.
Questo è solo il punto di partenza ma è anche talmente semplice ed utile che vale certamente il piccolo sforzo di implementazione.