So you're up for SOA, your plan, to use WCF and make your services distributed, but from most of the demos you've seen there's no example of security, what if your service is supposed to check for username/password? what if you're supposed to check for certificate thumbprints? what if you need to return different results for each logged in user/certain certificate? what if you need to add auditing? logging? you have 20+ Operations (remote methods) and implementing all of this is becoming a pain..
It might look like a headache, but fear not, in the end its pretty simple, I've wrote a demo project containing WCF services and WCF clients, I've mixed all the security I thought you might need to start and gave you options which one to use, its all down to how much fine grained security you want and how much time are you willing to spend.
So let's start with definitions so we're both on common ground.
Data Contracts
Data contracts are specifiers for your service/client, they specify what and how information is exchanged.
- Data Contracts are defined on the classes you want to be known for both service and client, for example, a Product object will contain an Id and Name.
The Product object will be decorated with DataContract attribute and each of the properties will be decorated with DataMember attribute.
[DataContract] class Product { [DataMember] public int Id { get; set; } [DataMember] public string Name { get; set; } }
Service Contracts
Service contracts are specifiers for your services, they specify what actions you may perform on the service, these actions are called Operations.
- Service Contracts are defined on the service interface you wish to expose and are decorated with ServiceContract attribute
- Operations are defined on the service interface (which later are implemented by the service class) and are decorated with the OperationContract attribute.
[ServiceContract] public interface IAAServiceDemo { [OperationContract] void DoWork(); }
public class AAServiceDemo : IAAServiceDemo { public void DoWork() { } }
Parameter Passing
- Parameters can be passed via the standard mechanism
int Method(int Parameter1)
- Or via ref/out (if writing another Data Contract for the return value is out of the question)
int Method(ref int param1, out int param2)
- Or via Headers (the call must be inside the context) - per call
Client - setting headers:
using (OperationContextScope scope = new OperationContextScope((IContextChannel)service.InnerChannel)) { OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("CustomHeaderValue", "ns", "Value CustomHeaderValue")); service.DoWork(); }
Service - reading headers:
MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders; if (headers.FindHeader("CustomHeaderValue", "ns") != -1) { var customheader = headers.GetHeader<string>("CustomHeaderValue", "ns"); if (customheader != null) { Helper.Log("DoWork() was called with custom header CustomHeaderValue: {0}", customheader); } }
But it can also work the other way around.
- Or for all messages, by implementing IClientMessageInspector (see code here and its behavior here)
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) { request.Headers.Add(MessageHeader.CreateHeader("InspectorHeader", "ns", "Value InspectorHeader")); return null; }
- Or if its UserName/Password via the ClientCredentials property.
ServiceDemoClient service = new ServiceDemoClient(); service.ClientCredentials.UserName.UserName = "username"; service.ClientCredentials.UserName.Password = "password";
- Or if its a certificate via the ClientCredentials property.
ServiceDemoClient service = new ServiceDemoClient(); service.ClientCredentials.ClientCertificate.SetCertificate(...);
- Or certificate through configuration
<clientCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/>
Behaviors
So what are behaviors?
Behaviors are certain activities you can add to your WCF code which will execute at certain times, behaviors cab be defined either by configuration or by attributes on the implementation part of the service.
- Via Configuration:
1. First we need to write a behavior extension, a behavior extension is defined by implementing BehaviorExtensionElement.
2. If you need additional configuration properties, they are defined by ConfigurationProperty attribute.
3. Once the extension configuration is done, WCF calls CreateBehavior() in the extension's implementation, which will return a new instance of the desired behavior.
4. A behavior extension is added to the configuration:
<system.serviceModel> <extensions> <behaviorExtensions> <add name="customInspector" type="WCFServer.CustomConfigurationSection, WCFServer" /> </behaviorExtensions> </extensions>
...
5. An extension is then applied to the desired behavior configuration:
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="behaviorWithCustomParameter"> ... <customInspector BehaviorName="WCFServer.Logging.LoggingBehavior,WCFServer" /> </behavior>
...
The simplest behavior extension can be seen in the demo project, CustomConfigurationSection.
- Via Attribute:
1. Decide what will be able to have your behavior:
IContractBehavior - Adds a behavior to entire contract, unlike IServiceBehavior and IEndpointBehavior it must be added programmatically through attributes.
IServiceBehavior - Adds a behavior to a service.
IEndpointBehavior - Adds a behavior to an endpoint in either service or client.
IOperationBehavior - Adds a behavior to a certain operation.
2. Create an attribute, implementing Attribute and behavior interface from step 1, for example to IServiceBehavior:
public class CustomBehaviorAttribute : Attribute, IServiceBehavior { }
3. Implement the appropriate ApplyDispatchBehavior, instantiate your handler/inspector and apply it to the channel/message/endpoint.
For example, applying the error handler:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers) { cd.ErrorHandlers.Add(new CustomErrorHandler()); } }
Message Inspector
Message Inspector are pieces of code that are executed after receiving requests and before sending replies, they can be attached either to the service side (making them good candidates for custom logging implementations) or the client side (making them good candidates to send any custom headers/checksums/etc').
- An inspector is added by a behavior.
A logging inspector implementation can be seen in the WCFServer.Logging namespace, there's a behavior configuration extension, a behavior and an inspector.
A security inspector implementation can be seen in the WCFServer.Security.CustomSecurityCheck namespace, its not recommended for use but it might provide an answer to a specific niche.
Invoker
An Invoker (implementing IOperationInvoker) is registered via behaviors, an invoker is invoked before each operation, it is implemented as both asynchronous and synchronous methods.
Here's an empty invoker implementation, you can fill in code before the instance's calls:
public class CustomSecurityCheckInvoker : IOperationInvoker { private readonly IOperationInvoker _invoker; public CustomSecurityCheckInvoker(IOperationInvoker invoker) { this._invoker = invoker; } #region IOperationInvoker Members public object[] AllocateInputs() { return _invoker.AllocateInputs(); } public object Invoke(object instance, object[] inputs, out object[] outputs) { //Your code here return _invoker.Invoke(instance, inputs, out outputs); //Or here } public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) { //Your code here return _invoker.InvokeBegin(instance, inputs, callback, state); } public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) { return _invoker.InvokeEnd(instance, out outputs, result); } public bool IsSynchronous { get { return _invoker.IsSynchronous; } } #endregion }
To inject this invoker to the execution chain, you'll need to implement a behavior, here's an example of a behavior that can be on either an operation or a service, the full code is in the demo project.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Interface)] public class CustomSecurityCheckAttribute : Attribute, IOperationBehavior, IServiceBehavior { #region IOperationBehavior Members public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParame { } public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation) { } public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) { //Injecting an invoker to the IOperationBehavior, note the passing of the previous invoker //so they are chained dispatchOperation.Invoker = new CustomSecurityCheckInvoker(dispatchOperation.Invoker); } public void Validate(OperationDescription operationDescription) { } #endregion #region IServiceBehavior Members public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.O { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { //Go over all endpoints of this service foreach (var endpoint in serviceHostBase.Description.Endpoints) { //for each operation (eg. method) foreach (var operation in endpoint.Contract.Operations) { //if an Invoker is already preset, merge Checklists, otherwise, create new if (operation.Behaviors.Contains(typeof(CustomSecurityCheckAttribute))) { var customsecuritychecksbehavior = operation.Behaviors[typeof(CustomSecurityCheckAttribute)] as CustomSecurityCheckAttribute; customsecuritychecksbehavior.CheckList.AddRange(this.CheckList); } else { operation.Behaviors.Add(new CustomSecurityCheckAttribute(CheckList)); } } } } public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { } #endregion
}
- Security via invoker:
Before activating the previous invoker instance in the chain, you can check for whatever conditions your code needs, there's an example in the WCFServer.Security.CustomSecurityCheck, but note that the credentials must be passed with a custom binding, otherwise WCF framework will remove the full credentials as soon as its out of the Authentication/Authorization managers and will leave you with identity only, we'll see how to do it a bit later.
There's a collection of access methods in WCFClient.Clients.ServiceUsing, they are all built on this example:
public static void Use<SVC>(Action<SVC> action) where SVC: ICommunicationObject { SVC service = Activator.CreateInstance<SVC>(); try { action(service); service.Close(); } catch (Exception e) { service.Abort(); throw e; } finally { ((IDisposable)service).Dispose(); } }
But other implementations (included in ServiceUsing.cs) are also handling ClientCredentials, username or certificate which are useful in different scenarios.
And of curse, the basics of WCF:
Endpoint
An endpoint is the communication channel through which a service can communicate with the client, it has the service interface (contract) so it knows what commands to listen to, it has the binding so it knows how the data is formatted.
Service
A service is the top level definition of a WCF service,it has the behavior so it knows how to communicate, for example does the service requires credentials? does it have a sophisticated security managers and policies? a service can have multiple endpoints so it can listen on many ports and know which contract (service interface) to use for each endpoint.
Now that we're all on the same page and we understand what's going on more or less, I'll attempt to address security, logging and error handling, I'll start with logging as its simpler, then go into error handling and lastly we'll take a look at some security options, starting with the simplest implementation and going into the more robust but also more complex, don't worry, once you understand the basics, almost nothing is too complex.
Client
A client is the other side of the service, it has a collection of endpoints which have matching bindings and behaviors to the service and therefore can communicate on the same level.
Logging
After discussing inspectors, behaviors and behavior extensions, its pretty simple to come up with a logging system, you can take a look in WCFServer.Logging namespace, there's a message inspector that is activated after each received request, creates a copy of the request (since a request can be read only once) and then take whatever we need from the message and log it.
LoggingMessageInspector - the inspector for the logging system.
LoggingBehavior - the behavior that applies the message inspectors to the endpoints
LoggingConfigurationSection - the configuration section for the logging system.
All we need to do after that is add a behavior extension in the configuration file and reference that extension in the behavior configuration, scroll back to behaviors if you can't remember how to do it.
Error Handling
WCF handles errors out of the box in a very simple way, if a specific error was not thrown as a fault, it will return a general error so not to expose the inner working of the service.
So here are our options for handling errors:
1. Ignore them - let the client guess what went wrong, not a good idea, but for highly secure system, this might be a good option rather than providing a clue to any hacking attempts.
2. Open up the service and send the exact exceptions thrown - this is not such a bad idea in closed systems as it could give the client a pretty good idea what went wrong and how to fix it.
Here's how you configure it, in the behavior configuration, add serviceDebug section that looks like this:
<serviceDebug includeExceptionDetailInFaults="true" />
3. Manage faults for each exception, could be a lot of work, but for small services is the best course of action, each method can fail in so many way but only 2-3 faults make sense, for example system down, invalid data and unauthorized.
The way faults are passed:
throw new FaultException("fault message", new FaultCode("fault code"));
when caught, you can distinguish between faults by comparing fault codes.
4. Custom faults - You can attach data contracts to faults, by attaching a custom structure (and catching by the same structure) you can have detailed faults, you HAVE to include this data contract in the Service interface, for each Operation expected to throw this contract by decorating the operation (or method) with FaultContract attribute (see example).
The way custom faults are passed:
throw new FaultException<CustomFault>(new CustomFault(error.Message,"additional text"), error.Message,new FaultCode("code123"),"On line 123");
And catch by these faults with:
try { } catch (FaultException<CustomFault> cf) { } catch (Exception ex) { }
5. Custom Error Handler - Custom error handlers is a way to handle all errors in your services, by implementing IErrorHandler you can control what and how errors are handled, which ones may pass on to the client and if you want special action taken, to be notified by email for example, it can be done there (though I wouldn't do anything that takes too long, or otherwise the client might time out).
Like other behaviors in WCF, you can do it either by configuration or by code or decorate with attributes, all you need to do is add the error handler to ApplyDispatchBehavior.
Note that if you're using custom faults, the client might not have a proxy of the data contract, so you might have to decorate one Operation with FaultContract attribute to force the proxy's creation.
You can find an example in the project under WCFServer.ErrorHandling namespace:
CustomFault - is the custom fault data contract, if you include this structure in your faults, you can populate it with the error information you pass back to the client (if reason and code are not enough).
CustomErrorHandler - the error handler which is actually getting executed on each error thrown, even if its a fault, so you should integrate logic to handle these too, in my example I'm checking if its a fault, if it is, I'm not touching the error.
CustomErrorAttribute - the custom error attribute/behavior, each service decorated with it, will use the custom error handler for handling errors.
6. Contract-Level FaultExceptions - In case you need good fault descriptions and more information to be passed along with the fault, but you don't want to decorate each Operation with FaultContract or the custom errors are not enough, there's one more option (though its unsupported by WCF) you might want to read David Turner's WCF: Contract-level FaultContract.
Great, so now we're done with understanding a basic WCF service, we can talk about security.
Why do we want security?
Well, security provides us a way to identify who wants to do what and in case we're talking about your bank account, we don't really want anyone else to touch ours.
So we said a good security will validate the user is who they claim to be, in WCF we can do that (mostly) in three ways, Username/Password, Certificate and Token, but the framework is not limited to that, if tomorrow another validation is invented, WCF can be configured to do that as well, you can pass the security identifiers - for example in the header and the server can be configured to validate based on that information.
Then, after the bank checks its us, it will let us in.
But supposed someone else caught our message, tampered with it and sent it to the bank, lets say, we asked the bank to transfer $1 to account number 3, but our dear hacker wants that $1, you know what? no, he wants $5 and he wants it transferred to account number 5.
That's where authentication comes in, authentication checks that it was actually us who asked to transfer the money, if it sees someone tampered with the message, it won't let it pass through.
Eventually, the bank will want to make sure you're allowed to transfer money, suppose your bank account is frozen, they don't want you to transfer anything, that's where authorization comes in, it makes sure you have the right to do what you want to do.
Its the same with WCF security:
Validation - the user is first checked if they should have access.
Authentication - a message (not the user!) is checked to see if its authentic and valid.
Authorization - the service checks what resources the user is allowed to see.
But we don't have to implement all these features, they are just that - features, we can mix and match security components to match our needs, though it wouldn't make much sense to have authorization but not validation...
So let's start.
Validation
1. Username/password validation - custom username verification is inheriting from UserNamePasswordValidator, when you create your verification class it should look something like this:
public class CustomUsernameValidator : UserNamePasswordValidator { public override void Validate(string userName, string password) { if (!UserStore.ValidateUser(userName, password)) { throw new SecurityTokenException("Unauthorized"); } } }
but by itself it doesn't do much, so we're going to have to tell the service which validation to use, we do that with two configuration items, a behavior which will tell the service to use username authentication and which certificate to use, since we don't want the username and password to be transmitted in clear text, it will use the certificate to encrypt that information.
<behavior name="simpleUsernameValidation"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="false"/> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServer.Security.CustomUsernameValidator, WCFServer"/> <serviceCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </serviceCredentials> </behavior>
And a binding that will tell it what to encrypt, either the transport or the message, here I'm using message encryption, but you can use which ever fits you.
<binding name="usernameHttpBinding"> <security mode="Message"> <message clientCredentialType="UserName"/> </security> </binding>
don't forget to add the behaviorConfiguration to the service and the bindingConfiguration to the endpoint.
2. Certificate validation - certificate validation is very similar to username/password validation, except it uses the certificate to perform validation, the X509Certificate2 object which the client used to access the service is passed to your implementation of X509CertificateValidator.
In this example I'm checking to see the subject and thumbprint are in my local user collection:
public class CustomCertificateValidator : X509CertificateValidator { public override void Validate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { if (!UserStore.ValidateUser(certificate.Subject + "; " + certificate.Thumbprint)) throw new SecurityTokenValidationException("Unauthorized"); } }
And a behavior:
<behavior name="CertificateValidation"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> <serviceCredentials> <clientCertificate> <authentication certificateValidationMode="Custom" customCertificateValidatorType="WCFServer.Security.CustomCertificateValidator, WCFServer"/> </clientCertificate> <serviceCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </serviceCredentials> </behavior>
Binding:
<binding name="certificateHttpBinding"> <security mode="Message"> <message clientCredentialType="Certificate" /> </security> </binding>
and ofcurse don't forget to add the behaviorConfiguration to the service and the bindingConfiguration to the endpoint.
3. Token validation - suffice to say its possible, but its out of the scope of this article, there's Rory Primrose's username token example, STS token, predefined tokens or any other token.
4. Windows based validation - there's an example by Ionut Paduraru.
Authentication
Authentication is making sure the message is valid, when you implement a custom authentication you'll inherit from ServiceAuthenticationManager and override Authenticate method where all your message authentication logic goes in.
Then you'll need to configure the new message authentication in the binding:
<behavior name="AuthorizationAuthenticationValidation"> ... <serviceAuthenticationManager serviceAuthenticationManagerType="WCFServer.Security.CustomAuthenticationManager, WCFServer" /> ... </behavior>
There are also token authenticators, but they are more related to user credentials, as they are there to make sure the credential is valid, you can find them in the System.IdentityModel.Selectors namespace, a few of them are CustomUserNameSecurityTokenAuthenticator, KerberosSecurityTokenAuthenticator, UserNameSecurityTokenAuthenticator, WindowsSecurityTokenAuthenticator and X509SecurityTokenAuthenticator, but the idea is the same, authenticate a token and make sure its valid.
Unless you have a very special case, I don't see a good enough reason to implement custom authentication.
Authorization
Authorization is the act of checking which credential has access to which resource, it can be as simple as allowing or denying access to an Operation, or as complicated as a specific action or resource inside an allowed Operation, it can be very simple by using roles or complex by using Claims.
There are two components of Authorization, a ServiceAuthorizationManager and Authorization Policy (implementing IAuthorizationPolicy), both can be used for creating an identity, roles and claims for an identity (user) and checking for access, but you can have only one Authorization Manager for a behavior but many Authorization Policies, so you can put your general operation checking in the authorization manager code and divide your policies.
Now that we know all the possibilities and understand what we want, we'll go into specific implementations on how to do it.
UserName/Password Validation
Suppose we want to check access to our service with username and password, we'll need to implement the following:
1. Create a validation class, implementing UserNamePasswordValidator abstract class, overriding the Validate method, so it will check the username and password that were passed are allowed access.
2. Configure the class we created in the behavior configuration AND adding a certificate for the service to use, WCF will not allow us to pass username/password in an unencrypted message.
<behavior name="simpleUsernameValidation"> ... <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServer.Security.CustomUsernameValidator, WCFServer"/> <serviceCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </serviceCredentials> ... </behavior>
3. Configure the binding to pass the credentials in the message.
<binding name="usernameHttpBinding"> <security mode="Message"> <message clientCredentialType="UserName"/> </security> </binding>
4. Configure the binding configuration and the behavior configuration in the service and endpoint.
5. Make sure the service endpoint's identity match the certificate, you can force it an a development environment by using:
<service ...> <endpoint ...> <identity> <dns value="sslstreamtest" /> </identity> </endpoint> ... </service>
6. If you're in a development environment, you can also override the check for a valid certificate in the client by adding a behavior configuration:
<behavior name="DisableCertificateValidation"> <clientCredentials> <serviceCertificate> <authentication certificateValidationMode="None" revocationMode="NoCheck" /> </serviceCertificate> </clientCredentials> </behavior>
Make sure you pass username/password in the ClientCredentials.UserName.UserName/Password and you're done.
Certificate Validation
Lets say username/passwords are not secure enough, you're worried that its too easy and want to check by a certificate instead, AWS uses certificates for all their API for example, how can we do that?
Easy.
1. Implement a validation class based on X509CertificateValidator.
For example:
public class CustomCertificateValidator : X509CertificateValidator { public override void Validate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { if (!UserStore.ValidateUser(certificate.Subject + "; " + certificate.Thumbprint)) throw new SecurityTokenValidationException("Unauthorized"); } }
2. Configure the class we created in the service behavior configuration and the service's certificate:
<behavior name="CertificateValidation"> ... <serviceCredentials> <clientCertificate> <authentication certificateValidationMode="Custom" customCertificateValidatorType="WCFServer.Security.CustomCertificateValidator, WCFServer"/> </clientCertificate> <serviceCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </serviceCredentials> ... </behavior>
3. Configure the binding:
<binding name="certificateHttpBinding"> <security mode="Message"> <message clientCredentialType="Certificate" /> </security> </binding>
4. Configure the binding configuration and the behavior configuration in the service and endpoint.
5. Make sure the service endpoint's identity match the certificate, you can force it an a development environment by using:
<service ...> <endpoint ...> <identity> <dns value="sslstreamtest" /> </identity> </endpoint> ... </service>
6. If you're in a development environment, you can also override the check for a valid certificate in the client by adding a behavior configuration:
<behavior name="DisableCertificateValidation"> <clientCredentials> <serviceCertificate> <authentication certificateValidationMode="None" revocationMode="NoCheck" /> </serviceCertificate> </clientCredentials> </behavior>
7. Configure the client to use a client certificate OR use code with ClientCredentials.ClientCertificate.SetCertificate(...)
<behavior name="CertificateClientCredentials"> <clientCredentials> ... <!--I'm using the same certificate for the client to simplify the demo, in production environment, you'll provide a certificate to each service--> <clientCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </clientCredentials> </behavior>
That's it.
Adding Roles
So how can we use .NET's implementation of role checking? easy, by implementing a principal, that principal will tell the framework if you have a role or not.
1. Implement IPrincipal and IsInRole.
class SimplePrincipal : IPrincipal { private IIdentity _identity; private string[] _roles; public SimplePrincipal(IIdentity identity, string[] roles) { _identity = identity; _roles = roles; } #region IPrincipal Members public IIdentity Identity { get { return _identity; } } public bool IsInRole(string role) { return _roles.Contains(role); } #endregion }
2. Implement an Authorization Policy that will push that principal into the context based on the logged in user, here's an example to use the username, but you can also use a certificate, we'll see that later.
class SimpleAuthorizationPolicy : IAuthorizationPolicy { #region IAuthorizationPolicy Members public bool Evaluate(EvaluationContext evaluationContext, ref object state) { if (evaluationContext.Properties.ContainsKey("Identities")) { List<IIdentity> identities = evaluationContext.Properties["Identities"] as List<IIdentity>; IIdentity identity = identities.FirstOrDefault(i => i.AuthenticationType == "CustomUsernameValidator"); var user = UserStore.GetUserByUsername(identity.Name); SimplePrincipal simpleprincipal = new SimplePrincipal(identity,user.Roles ); evaluationContext.Properties["Principal"] = simpleprincipal; return true; } else return false; } public System.IdentityModel.Claims.ClaimSet Issuer { get; private set; } #endregion #region IAuthorizationComponent Members public string Id { get; private set; } #endregion }
3. Configure the policy in the behavior configuration.
4. Add the PrincipalPermission attribute to your operation implementation:
[PrincipalPermission(SecurityAction.Demand, Role = "API")] public void TestRoleAPI() { }
You can see the implementation in the WCFServer.Security.Simple, its applied to the service in WCFServer.Services.SimpleUsername and configured on SimpleUsernameServiceDemo service.
Claims
Claims are one of the most versatile security system in .NET.
So what are claims?
Claims are pieces of information that describe the identity (or user) inside WCF.
For example, a User Id is a claim, a User Role is a claim, a user's Country is claim.
So how can we use claims to our benefit?
By providing a complete ClaimSet of all the user's information needed by the service, we can have the service allow or deny access, return a different set of results for each identity and even more.
There's a free ebook written by Microsoft, unfortunately I haven't found the time to read it yet, just quickly skimmed through it, if you're planning to implement Claims based security, I would recommend reading more about it.
So how about an example?
There's a demo service in WCFServer.Services.AA namespace.
The configuration is exactly like Username/Password, but notice the implementation CustomUsernameAuthorizationPolicy - Evaluate:
GenericPrincipal genprincipal = new GenericPrincipal(identity, null); evaluationContext.Properties["Principal"] = genprincipal; var user = UserStore.GetUserByUsername(identity.Name); evaluationContext.AddClaimSet(this, new DefaultClaimSet(new Claim("User", user, Rights.Identity)));
We're adding a new ClaimSet with the User object, so next we can use it in our service's implementation.
If you take a look inside WCFServer.Security.Claims, you can find there the actual work:
CheckClaims - a helper function to check for permission and get the Claim's values.
CheckClaimInvoker - the invoker that will be executed before calling the Operation, it will check for claims.
CheckClaimAttribute - a behavior that will add the claim checking invoker.
Then, I'm adding the behavior CheckClaim attribute to the AAServiceDemo Operation DoWork, it looks like that:
[CheckClaim("User", "http://schemas.xmlsoap.org/ws/2005/05/identity/right/identity", true)] public void DoWork() { var user = CheckClaim.GetClaim<User>("User", Rights.Identity); }
Inside the operation, I'm calling CheckClaim.GetClaim which is actually:
AuthorizationContext authContext = ServiceSecurityContext.Current.AuthorizationContext; var user = (User)authContext.ClaimSets.SelectMany(i => i.FindClaims("User", Rights.Identity)).FirstOrDefault().Resource;
And that's it.
So if claims based security is the best thing I could ask, what's left?
Invoker Based Security
Well, just for the sake of completeness, I've decided to include a sort of invoker security method, it takes some of the WCF options and shows an example how flexible WCF can be.
You can find the example at WCFServer.Services.Invoker namespace.
Its pretty straight forward:
1. configure the behavior to use custom username/password validation:
<behavior name="InvokerBehavior"> ... <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServer.Security.CustomUsernameValidator, WCFServer"/> <serviceCertificate findValue="sslstreamtest" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My"/> </serviceCredentials> ... </behavior>
2. configure a custom binding with UserNameOverTransport, the reason for this is that we want to keep the username/password inside the IncomingSupportingTokens after the verification stage.
<customBinding> <binding name="binaryBinding"> <security authenticationMode="UserNameForSslNegotiated"> <secureConversationBootstrap authenticationMode="UserNameOverTransport" /> </security> <binaryMessa1geEncoding /> <httpTransport maxReceivedMessageSize="10000000" maxBufferSize="10000000" /> </binding> </customBinding>
3. do any type of security checks we want with:
UserNameSecurityToken securityToken = OperationContext.Current.IncomingMessageProperties.Security.IncomingSupportingTokens[0].SecurityToken as System.IdentityModel.Tokens.UserNameSecurityToken; string username = securityToken.UserName; string password = securityToken.Password;
You can see a few examples in a custom behavior attribute - CustomSecurityCheckAttribute, and the invoker its using - CustomSecurityCheckInvoker.
In any case, if you want a default behavior or binding configurations, just add one with an empty name.
That's it, you're ready to to start learning how to implement the security features of your dreams, you can take a look at a demo service/client here, I've mixed and matched all the security features for this article:
https://github.com/drorgl/ForBlog/tree/master/WCFPermissions
0 comments:
Post a Comment