2. Constraining Message Protection
While a service should ideally use the highest possible
level of security, it is actually at the mercy of its host, because
the host is the one configuring the binding. This is especially
problematic if the service is to be deployed in an unknown environment
with an arbitrary host. To compensate, WCF lets service developers
insist on a protection level, or rather, constrain the minimum
protection level at which their service is willing to operate. Both
the service and the client can constrain the protection level,
independently of each other. You can constrain the protection level in
three places. When constrained at the service contract, all operations
on the contract are considered sensitive and protected. When
constrained at the operation contract, only that operation is
protected; other operations on the same contract are not. Finally, you
can constrain the protection level for an individual fault contract.
This can be required because sometimes the error information returned
to the client is sensitive, containing parameter values, exception
messages, and the call stack. The respective contract attributes offer
the ProtectionLevel property of the
enum type ProtectionLevel:
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,
Inherited = false)]
public sealed class ServiceContractAttribute : Attribute
{
public ProtectionLevel ProtectionLevel
{get;set;}
//More members
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
public ProtectionLevel ProtectionLevel
{get;set;}
//More members
}
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true,
Inherited = false)]
public sealed class FaultContractAttribute : Attribute
{
public ProtectionLevel ProtectionLevel
{get;set;}
//More members
}
As an example, here is how to set the protection level on a
service contract:
[ServiceContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
interface IMyContract
{...}
Setting the ProtectionLevel
property on the contract attributes merely indicates the low-water
mark; that is, the minimum protection level accepted by this contract.
If the binding is configured for a lower
protection level, it will result in an InvalidOperationException at the
service load time or the time the proxy is opened. If the binding is
configured for a higher level, the contract will use that level. The
ProtectionLevel property on the contract
attributes defaults to ProtectionLevel.None,
meaning it has no effect.
The desired protection constraint is considered a local
implementation detail of the service, so the required protection level
is not exported with the service metadata. Consequently, the client
may require a different level and enforce it separately from the
service.
Note:
Even though the Internet bindings do not offer a protection
level property, the protection level constraint at the service-,
operation-, or fault-contract level is satisfied when using
Transport or Message security. The constraint is not satisfied when
security is turned off by using the None security mode.
3. Authentication
By default, when a client calls a proxy that targets an
endpoint whose binding is configured for using Windows credentials
with Transport security, there is nothing explicit the client needs to
do to pass its credentials. WCF will automatically pass the Windows
identity of the client’s process to the service:
class MyContractClient : ClientBase<IMyContract>,IMyContract
{...}
MyContractClient proxy = new MyContractClient();
proxy.MyMethod(); //Client identity passed here
proxy.Close();
When the service receives the call, WCF will authenticate the
caller on the service side. If the client’s credentials represent a
valid Windows account, the caller will be allowed to access the
requested operation on the service.
3.1. Providing alternative Windows credentials
Instead of using the identity of the process in which it
happens to be running, the client can pass alternative Windows
credentials. The ClientBase<T> base class offers the
ClientCredentials property of the
type ClientCredentials:
public abstract class ClientBase<T> : ...
{
public ClientCredentials ClientCredentials
{get;}
}
public class ClientCredentials : ...,IEndpointBehavior
{
public WindowsClientCredential Windows
{get;}
//More members
}
ClientCredentials contains the property
Windows of the type WindowsClientCredential, defined as:
public sealed class WindowsClientCredential
{
public NetworkCredential ClientCredential
{get;set;}
//More members
}
WindowsClientCredential has the property
ClientCredential of the type
NetworkCredential, which is
where the client needs to set the alternative credentials:
public class NetworkCredential : ...
{
public NetworkCredential();
public NetworkCredential(string userName,string password);
public NetworkCredential(string userName,string password,string domain);
public string Domain
{get;set;}
public string UserName
{get;set;}
public string Password
{get;set;}
}
Example 1
demonstrates how to use these classes and properties to provide
alternative Windows credentials.
Example 1. Providing alternative Windows credentials
MyContractClient proxy = new MyContractClient();
proxy.ClientCredentials.Windows.ClientCredential.Domain = "MyDomain"; proxy.ClientCredentials.Windows.ClientCredential.UserName = "MyUsername"; proxy.ClientCredentials.Windows.ClientCredential.Password = "MyPassword";
proxy.MyMethod(); proxy.Close();
|
Once you specify an alternative identity and open the proxy,
the proxy cannot use any other identity later.
Warning:
If you do try specifying alternative credentials after
opening the proxy, those credentials will be silently
ignored.
Clients can use the technique demonstrated in Example 1 when the
credentials provided are collected dynamically at runtime, perhaps
using a login dialog box.
When working with a channel factory
instead of a proxy class, the ChannelFactory base class offers the
Credentials property of the type
ClientCredentials:
public abstract class ChannelFactory : ...
{
public ClientCredentials Credentials
{get;}
//More members
}
public class ChannelFactory<T> : ChannelFactory,...
{
public T CreateChannel();
//More members
}
In this case, simply set the alternative credentials in the
Credentials property, as was done
in Example 1:
ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(...);
factory.Credentials.Windows.ClientCredential.Domain = "MyDomain";
factory.Credentials.Windows.ClientCredential.UserName = "MyUsername";
factory.Credentials.Windows.ClientCredential.Password = "MyPassword";
IMyContract proxy = factory.CreateChannel();
Note that you cannot use the static CreateChannel() methods of ChannelFactory<T>, since you have to
first instantiate a factory in order to access the Credentials property.