WCF ako platforma pre distribuované programovanie obsahuje viacero mechanizmov na ochranu prenášaných dát a správu prístupu. Pozrime sa na základné možnosti, ktoré ponúka WCF v oblasti security.
reklama
WCF ako platforma pre distribuované programovanie obsahuje viacero mechanizmov na ochranu prenášaných dát a správu prístupu. Pozrime sa na základné možnosti, ktoré ponúka WCF v oblasti security.
Základné zložky WCF security:
- zabezpečenie prenosu (transfer security),
- kontrola prístupu (access control),
- logovanie dôležitých udalostí (auditing).
WCF ponúka dve základné možnosti pre zabezpečenie prenosu:
- transport security mode - security je implementovaná na úrovni prenosového protokolu (napr. HTTPS),
- message security mode - security je implementovaná na úrovni SOAP správ (napr. WS-Security).
WCF defaultne podporuje tieto základné credentials:
- Windows credentials - intranetové aplikácie,
- UserName tokens - B2C aplikácie,
- CardSpace - B2C aplikácie,
- Certifikáty - B2B aplikácie,
- Credentials (Security Token Service) - B2B aplikácie.
Príklad
Príklad znázorňuje scenár pre B2C aplikáciu. Komunikácia medzi klientom a službou bude zabezpečená na úrovni posielaných správ (message-level security). Služba bude autentifikovaná pomocou certifikátu, klient bude autentifikovaný na základe užívateľského mena a hesla. Autorizácia bude implementovaná pomocou claim-ov (claims-based model). Claim je charakterizovaný svojím typom, zdrojom s ktorým je asociovaný a oprávnením. Pre lepšie pochopenie pojmu claim odporúčam pozrieť sa do dokumentácie na MSDN na triedu System.IdentityModel.Claims.Claim. Okrem tohto mechanizmu môžeme autorizáciu vo WCF realizovať pomocou známych CLR konštrukcií (role-based model).
Service
Služba, ktorú sa budeme snažiť nejako zabezpečiť bude predstavovať správcu úloh. Kontrakt služby je definovaný rozhraním ITaskManager. Implementačná trieda služby je jednoduchá, zabezpečuje pridávanie úloh do zoznamu a poskytnutie úloh zo zoznamu. C# kód kontraktu služby a implementačnej triedy služby bude:
using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace TaskManagerService
{
[ServiceContract(Namespace = "http://TaskManagerService")]
public interface ITaskManager
{
[OperationContract]
void AddTask(string task);
[OperationContract]
string[] GetTasks();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class TaskManager : ITaskManager
{
private List<string> tasks = new List<string>();
#region ITaskManager Members
public void AddTask(string task)
{
tasks.Add(task);
}
public string[] GetTasks()
{
return tasks.ToArray();
}
#endregion
}
}
Mechanizmus autentifikácie na základe užívateľského mena a hesla je implementovaný v triede TaskManagerUserNamePasswordValidator odvodenej od abstraktnej triedy System.IdentityModel.Selectors.UserNamePasswordValidator. Samotná autentifikačná logika je v prekrytej metóde Validate:
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.IdentityModel.Selectors;
namespace TaskManagerService
{
public class TaskManagerUserNamePasswordValidator : UserNamePasswordValidator
{
private Dictionary users = new Dictionary();
public TaskManagerUserNamePasswordValidator()
{
users.Add("employee", "wcf");
users.Add("manager", "wcf");
}
public override void Validate(string userName, string password)
{
Console.WriteLine("userName = {0}, password = {1}", userName, password);
if (userName == null || password == null)
throw new ArgumentNullException();
if (!(users.ContainsKey(userName) && users[userName] == password))
throw new SecurityTokenException("Unknown Username or Password");
}
}
}
Prvá časť autorizačného mechanizmu je implementovaná v triede TaskManagerAuthorizationPolicy implementujúcej rozhranie System.IdentityModel.Policy.IAuthorizationPolicy. Úlohou tejto triedy je priradiť k nejakej identite podľa užívateľského mena príslušné claim-y reprezentujúce oprávnenia na volanie operácií služby:
using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.IdentityModel.Policy;
namespace TaskManagerService
{
public class TaskManagerAuthorizationPolicy : IAuthorizationPolicy
{
string id = Guid.NewGuid().ToString();
#region IAuthorizationPolicy Members
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
Console.WriteLine("TaskManagerAuthorizationPolicy.Evaluate");
CustomState customState;
if (state == null)
{
customState = new CustomState();
state = customState;
}
else
{
customState = state as CustomState;
}
if (!customState.ClaimsAdded)
{
List claims = new List();
foreach (ClaimSet cs in evaluationContext.ClaimSets)
{
foreach (Claim c in cs.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
{
foreach (string s in GetAllowedOperations(c.Resource.ToString()))
{
Console.WriteLine(" Claim.Resource: " + c.Resource);
claims.Add(
new Claim("http://TaskManagerService/claims/AllowedOperation",
s,
Rights.PossessProperty)
);
Console.WriteLine(" Claim added: " + s);
}
}
}
evaluationContext.AddClaimSet(this, new DefaultClaimSet(this.Issuer, claims));
customState.ClaimsAdded = true;
}
return true;
}
public ClaimSet Issuer
{
get { return ClaimSet.System; }
}
#endregion
#region IAuthorizationComponent Members
public string Id
{
get { return id; }
}
#endregion
private static IEnumerable GetAllowedOperations(string userName)
{
List operations = new List();
if (userName == "manager")
{
operations.Add("http://TaskManagerService/ITaskManager/AddTask");
operations.Add("http://TaskManagerService/ITaskManager/GetTasks");
}
else if (userName == "employee")
{
operations.Add("http://TaskManagerService/ITaskManager/GetTasks");
}
return operations;
}
class CustomState
{
private bool claimsAdded;
public bool ClaimsAdded
{
get { return claimsAdded; }
set { claimsAdded = value; }
}
}
}
}
Druhá časť autorizačného mechanizmu je implementovaná v triede TaskManagerAuthorization odvodenej od triedy System.ServiceModel.ServiceAuthorizationManager. Samotné rozhodnutie o autorizácii je reprezentované návratovou hodnotou prekrytej metódy CheckAccessCore, ktorá je volaná infraštruktúrou WCF:
using System;
using System.ServiceModel;
using System.IdentityModel.Claims;
namespace TaskManagerService
{
public class TaskManagerAuthorization : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
string action = operationContext.RequestContext.RequestMessage.Headers.Action;
Console.WriteLine("\nTaskManagerAuthorization.CheckAccessCore");
Console.WriteLine("Action: " + action);
foreach (ClaimSet cs in
operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
if (cs.Issuer == ClaimSet.System)
{
foreach (Claim c in
cs.FindClaims("http://TaskManagerService/claims/AllowedOperation",
Rights.PossessProperty))
{
Console.WriteLine("Resource: " + c.Resource);
if (action == c.Resource.ToString()) return true;
}
}
}
return false;
}
}
}
Host
Služba je hostovaná v konzolovej aplikácii, ktorej C# kód je:
using System;
using System.ServiceModel;
using TaskManagerService;
namespace Host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(TaskManager)))
{
host.Open();
Console.WriteLine("Service started at base address: " + host.BaseAddresses[0]);
Console.ReadLine();
}
}
}
}
V konfiguračnom súbore hostovacej aplikácie nastavíme security mód, certifikát pre službu, spôsob autentifikácie a autorizácie klientov služby:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="TaskManagerService.TaskManager"
behaviorConfiguration="TaskManagerBehavior">
<endpoint address="TaskManager"
binding="wsHttpBinding"
bindingConfiguration="SecuredBinding"
contract="TaskManagerService.ITaskManager" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="SecuredBinding">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="TaskManagerBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceCredentials>
<serviceCertificate findValue="localhost" storeLocation="LocalMachine"
storeName="My" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"TaskManagerService.TaskManagerUserNamePasswordValidator,
TaskManagerService" />
</serviceCredentials>
<serviceAuthorization serviceAuthorizationManagerType=
"TaskManagerService.TaskManagerAuthorization, TaskManagerService">
<authorizationPolicies>
<add policyType=
"TaskManagerService.TaskManagerAuthorizationPolicy,
TaskManagerService" />
</authorizationPolicies>
</serviceAuthorization>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Klient
V klientskej konzolovej aplikácii pred volaním operácií služby nastavíme prihlasovacie meno a heslo:
using System;
using Client.TaskManager;
namespace Client
{
class Program
{
static void Main(string[] args)
{
using (TaskManagerClient client = new TaskManagerClient())
{
client.ClientCredentials.UserName.UserName = "manager";
client.ClientCredentials.UserName.Password = "wcf";
client.AddTask("Task 1");
client.AddTask("Task 2");
client.AddTask("Task 3");
client.AddTask("Task 4");
foreach (string task in client.GetTasks())
{
Console.WriteLine(task);
}
Console.ReadLine();
}
}
}
}
V konfiguračnom súbore klienta nastavíme security mód a spôsob validácie certifikátov:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:8080/TaskManager"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBindingITaskManager"
contract="Client.TaskManager.ITaskManager"
behaviorConfiguration="EndpointBehavior" />
</client>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBindingITaskManager">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="EndpointBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Pred spustením služby a klienta je potrebné vytvoriť a nainštalovať certifikát pre službu príkazmi:
makecert -sr LocalMachine -ss MY -a sha1 -n CN=localhost -sky exchange -pe wcfservice.cer
certmgr -add -r LocalMachine -s My -c -n localhost -r CurrentUser -s TrustedPeople
Po spustení služby a klienta uvidíme v okne hostovacej aplikácie služby:

Uvedený príklad ukazuje iba jeden z možných prípadov implementácie security vo WCF. Security môžeme implementovať viacerými ďalšími spôsobmi. Výber záleží najmä od toho, v akom prostredí bude aplikácia nasadená. Zdrojové kódy k príkladu si môžete stiahnúť tu.
Ing. Marián Košťál (csharp@azet.sk)