Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En el ejemplo De tokens auxiliares se muestra cómo agregar tokens adicionales a un mensaje que usa WS-Security. En el ejemplo se agrega un token de seguridad binario X.509 además de un token de seguridad de nombre de usuario. El token se pasa en un encabezado de mensaje WS-Security desde el cliente al servicio, y parte del mensaje se firma con la clave privada asociada al token de seguridad X.509 para demostrar que se posee el certificado X.509 al destinatario. Esto es útil en el caso de que se requiera tener múltiples declaraciones asociadas con un mensaje para autenticar o autorizar al remitente. El servicio implementa un contrato que define un patrón de comunicación de solicitud-respuesta.
Demostraciones
En el ejemplo se muestra lo siguiente:
Cómo un cliente puede pasar tokens de seguridad adicionales a un servicio.
Cómo el servidor puede acceder a las reclamaciones asociadas con tokens de seguridad adicionales.
Cómo se usa el certificado X.509 del servidor para proteger la clave simétrica utilizada para el cifrado y la firma de mensajes.
Nota:
El procedimiento de instalación y las instrucciones de compilación de este ejemplo se encuentran al final de este tema.
El cliente se autentica con el token de nombre de usuario y el token de seguridad compatible con X.509
El servicio expone un único punto de conexión para comunicarse que se crea de forma programática mediante las clases BindingHelper y EchoServiceHost. El punto de conexión está compuesto por una dirección, un enlace y un contrato. El enlace se configura con un enlace personalizado utilizando SymmetricSecurityBindingElement y HttpTransportBindingElement. Este ejemplo establece SymmetricSecurityBindingElement para utilizar un certificado X.509 de servicio para proteger la clave simétrica durante la transmisión y pasar UserNameToken junto con el X509SecurityToken compatible en un encabezado de mensaje de WS-Security. La clave simétrica se usa para cifrar el cuerpo del mensaje y el token de seguridad de nombre de usuario. El token auxiliar se pasa como un token de seguridad binario adicional en el encabezado de mensaje de WS-Security. La autenticidad del token auxiliar se demuestra mediante la firma de parte del mensaje con la clave privada asociada al token de seguridad X.509 compatible.
public static Binding CreateMultiFactorAuthenticationBinding()
{
HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();
// the message security binding element will be configured to require 2 tokens:
// 1) A username-password encrypted with the service token
// 2) A client certificate used to sign the message
// Instantiate a binding element that will require the username/password token in the message (encrypted with the server cert)
SymmetricSecurityBindingElement messageSecurity = SecurityBindingElement.CreateUserNameForCertificateBindingElement();
// Create supporting token parameters for the client X509 certificate.
X509SecurityTokenParameters clientX509SupportingTokenParameters = new X509SecurityTokenParameters();
// Specify that the supporting token is passed in message send by the client to the service
clientX509SupportingTokenParameters.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
// Turn off derived keys
clientX509SupportingTokenParameters.RequireDerivedKeys = false;
// Augment the binding element to require the client's X509 certificate as an endorsing token in the message
messageSecurity.EndpointSupportingTokenParameters.Endorsing.Add(clientX509SupportingTokenParameters);
// Create a CustomBinding based on the constructed security binding element.
return new CustomBinding(messageSecurity, httpTransport);
}
El comportamiento especifica las credenciales de servicio que se van a usar para la autenticación de cliente y también información sobre el certificado X.509 de servicio. El ejemplo utiliza CN=localhost como un nombre de asunto en el certificado X.509 del servicio.
override protected void InitializeRuntime()
{
// Extract the ServiceCredentials behavior or create one.
ServiceCredentials serviceCredentials =
this.Description.Behaviors.Find<ServiceCredentials>();
if (serviceCredentials == null)
{
serviceCredentials = new ServiceCredentials();
this.Description.Behaviors.Add(serviceCredentials);
}
// Set the service certificate
serviceCredentials.ServiceCertificate.SetCertificate(
"CN=localhost");
/*
Setting the CertificateValidationMode to PeerOrChainTrust means that if the certificate is in the Trusted People store, then it will be trusted without performing a validation of the certificate's issuer chain. This setting is used here for convenience so that the sample can be run without having to have certificates issued by a certification authority (CA).
This setting is less secure than the default, ChainTrust. The security implications of this setting should be carefully considered before using PeerOrChainTrust in production code.
*/
serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
// Create the custom binding and add an endpoint to the service.
Binding multipleTokensBinding =
BindingHelper.CreateMultiFactorAuthenticationBinding();
this.AddServiceEndpoint(typeof(IEchoService),
multipleTokensBinding, string.Empty);
base.InitializeRuntime();
}
Código de servicio:
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class EchoService : IEchoService
{
public string Echo()
{
string userName;
string certificateSubjectName;
GetCallerIdentities(
OperationContext.Current.ServiceSecurityContext,
out userName,
out certificateSubjectName);
return $"Hello {userName}, {certificateSubjectName}";
}
public void Dispose()
{
}
bool TryGetClaimValue<TClaimResource>(ClaimSet claimSet,
string claimType, out TClaimResource resourceValue)
where TClaimResource : class
{
resourceValue = default(TClaimResource);
IEnumerable<Claim> matchingClaims =
claimSet.FindClaims(claimType, Rights.PossessProperty);
if(matchingClaims == null)
return false;
IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
if (enumerator.MoveNext())
{
resourceValue =
(enumerator.Current.Resource == null) ? null :
(enumerator.Current.Resource as TClaimResource);
return true;
}
else
{
return false;
}
}
// Returns the username and certificate subject name provided by
//the client
void GetCallerIdentities(ServiceSecurityContext
callerSecurityContext,
out string userName, out string certificateSubjectName)
{
userName = null;
certificateSubjectName = null;
// Look in all the claimsets in the authorization context
foreach (ClaimSet claimSet in
callerSecurityContext.AuthorizationContext.ClaimSets)
{
if (claimSet is WindowsClaimSet)
{
// Try to find a Name claim. This will have been
// generated from the windows username.
string tmpName;
if (TryGetClaimValue<string>(claimSet, ClaimTypes.Name,
out tmpName))
{
userName = tmpName;
}
}
else if (claimSet is X509CertificateClaimSet)
{
// Try to find an X500DistinguishedName claim. This will
// have been generated from the client certificate.
X500DistinguishedName tmpDistinguishedName;
if (TryGetClaimValue<X500DistinguishedName>(claimSet,
ClaimTypes.X500DistinguishedName,
out tmpDistinguishedName))
{
certificateSubjectName = tmpDistinguishedName.Name;
}
}
}
}
}
El punto de conexión de cliente se configura de forma similar al punto de conexión de servicio. El cliente usa la misma BindingHelper clase para crear un enlace. El resto de la configuración se encuentra en Client clase. El cliente establece información sobre el token de seguridad del nombre de usuario, el token de seguridad X.509 compatible y la información sobre el certificado X.509 de servicio en el código de configuración en la colección de comportamientos del extremo del cliente.
static void Main()
{
// Create the custom binding and an endpoint address for
// the service.
Binding multipleTokensBinding =
BindingHelper.CreateMultiFactorAuthenticationBinding();
EndpointAddress serviceAddress = new EndpointAddress(
"http://localhost/servicemodelsamples/service.svc");
ChannelFactory<IEchoService> channelFactory = null;
IEchoService client = null;
Console.WriteLine("Username authentication required.");
Console.WriteLine(
"Provide a valid machine or domain account. [domain\\user]");
Console.WriteLine(" Enter username:");
string username = Console.ReadLine();
Console.WriteLine(" Enter password:");
string password = "";
ConsoleKeyInfo info = Console.ReadKey(true);
while (info.Key != ConsoleKey.Enter)
{
if (info.Key != ConsoleKey.Backspace)
{
if (info.KeyChar != '\0')
{
password += info.KeyChar;
}
info = Console.ReadKey(true);
}
else if (info.Key == ConsoleKey.Backspace)
{
if (password != "")
{
password =
password.Substring(0, password.Length - 1);
}
info = Console.ReadKey(true);
}
}
for (int i = 0; i < password.Length; i++)
Console.Write("*");
Console.WriteLine();
try
{
// Create a proxy with the previously create binding and
// endpoint address
channelFactory =
new ChannelFactory<IEchoService>(
multipleTokensBinding, serviceAddress);
// configure the username credentials, the client
// certificate and the server certificate on the channel
// factory
channelFactory.Credentials.UserName.UserName = username;
channelFactory.Credentials.UserName.Password = password;
channelFactory.Credentials.ClientCertificate.SetCertificate(
"CN=client.com", StoreLocation.CurrentUser, StoreName.My);
channelFactory.Credentials.ServiceCertificate.SetDefaultCertificate(
"CN=localhost", StoreLocation.LocalMachine, StoreName.My);
client = channelFactory.CreateChannel();
Console.WriteLine("Echo service returned: {0}",
client.Echo());
((IChannel)client).Close();
channelFactory.Close();
}
catch (CommunicationException e)
{
Abort((IChannel)client, channelFactory);
// if there is a fault then print it out
FaultException fe = null;
Exception tmp = e;
while (tmp != null)
{
fe = tmp as FaultException;
if (fe != null)
{
break;
}
tmp = tmp.InnerException;
}
if (fe != null)
{
Console.WriteLine("The server sent back a fault: {0}",
fe.CreateMessageFault().Reason.GetMatchingTranslation().Text);
}
else
{
Console.WriteLine("The request failed with exception: {0}",e);
}
}
catch (TimeoutException)
{
Abort((IChannel)client, channelFactory);
Console.WriteLine("The request timed out");
}
catch (Exception e)
{
Abort((IChannel)client, channelFactory);
Console.WriteLine(
"The request failed with unexpected exception: {0}", e);
}
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
Mostrar la información de los autores de llamadas
Para mostrar la información del llamante, puede usar ServiceSecurityContext.Current.AuthorizationContext.ClaimSets como se muestra en el código siguiente. El ServiceSecurityContext.Current.AuthorizationContext.ClaimSets contiene atribuciones de autorización asociadas a la persona que realiza la llamada actual. Windows Communication Foundation (WCF) proporciona automáticamente esas reclamaciones para cada token recibido en el mensaje.
bool TryGetClaimValue<TClaimResource>(ClaimSet claimSet, string
claimType, out TClaimResource resourceValue)
where TClaimResource : class
{
resourceValue = default(TClaimResource);
IEnumerable<Claim> matchingClaims =
claimSet.FindClaims(claimType, Rights.PossessProperty);
if (matchingClaims == null)
return false;
IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
if (enumerator.MoveNext())
{
resourceValue = (enumerator.Current.Resource == null) ? null : (enumerator.Current.Resource as TClaimResource);
return true;
}
else
{
return false;
}
}
// Returns the username and certificate subject name provided by the client
void GetCallerIdentities(ServiceSecurityContext callerSecurityContext, out string userName, out string certificateSubjectName)
{
userName = null;
certificateSubjectName = null;
// Look in all the claimsets in the authorization context
foreach (ClaimSet claimSet in
callerSecurityContext.AuthorizationContext.ClaimSets)
{
if (claimSet is WindowsClaimSet)
{
// Try to find a Name claim. This will have been generated
//from the windows username.
string tmpName;
if (TryGetClaimValue<string>(claimSet, ClaimTypes.Name,
out tmpName))
{
userName = tmpName;
}
}
else if (claimSet is X509CertificateClaimSet)
{
//Try to find an X500DistinguishedName claim.
//This will have been generated from the client
//certificate.
X500DistinguishedName tmpDistinguishedName;
if (TryGetClaimValue<X500DistinguishedName>(claimSet,
ClaimTypes.X500DistinguishedName,
out tmpDistinguishedName))
{
certificateSubjectName = tmpDistinguishedName.Name;
}
}
}
}
Ejecución del ejemplo
Al ejecutar el ejemplo, el cliente le pide primero que proporcione el nombre de usuario y la contraseña para el token de nombre de usuario. Asegúrese de proporcionar valores correctos para la cuenta del sistema, ya que WCF en el servicio asigna los valores proporcionados en el token de nombre de usuario a la identidad proporcionada por el sistema. Después, el cliente muestra la respuesta del servicio. Presione ENTRAR en la ventana del cliente para apagar el cliente.
Instalar el archivo por lotes
El archivo por lotes de Setup.bat incluido con este ejemplo permite configurar el servidor con certificados pertinentes para ejecutar la aplicación hospedada de Internet Information Services (IIS) que requiere seguridad basada en certificados de servidor. Este archivo por lotes debe modificarse para que funcione a través de los equipos o en un caso no hospedado.
A continuación se proporciona una breve introducción a las distintas secciones de los archivos por lotes para que se puedan modificar para que se ejecuten en la configuración adecuada.
Creación del certificado de cliente
Las siguientes líneas del Setup.bat archivo por lotes crean el certificado de cliente que se va a usar. La %CLIENT_NAME% variable especifica el asunto del certificado de cliente. En este ejemplo se usa "client.com" como nombre del firmante.
El certificado está almacenado en Mi almacén (Personal) en la ubicación de almacén CurrentUser.
echo ************
echo making client cert
echo ************
makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe
Instalación del certificado de cliente en el almacén de confianza del servidor
La línea siguiente del archivo por lotes Setup.bat copia el certificado de cliente en el almacén de confianza del servidor. Este paso es necesario porque el sistema del servidor no confía implícitamente en los certificados generados por Makecert.exe. Si ya tiene un certificado que se basa en un certificado raíz de confianza de cliente (por ejemplo, un certificado emitido por Microsoft), este paso para rellenar el almacén de certificados de cliente con el certificado de servidor no es necesario.
echo ************
echo copying client cert to server's CurrentUserstore
echo ************
certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople
Creación del certificado de servidor
Las líneas siguientes del archivo por lotes de Setup.bat crean el certificado de servidor que se va a usar. La variable %SERVER_NAME%especifica el nombre del servidor. Cambie esta variable para especificar su propio nombre de servidor. El valor predeterminado en este archivo por lotes es el host local.
El certificado se almacena en el almacén My (Personal), en la ubicación de almacenamiento LocalMachine. El certificado se almacena en el almacén LocalMachine para los servicios hospedados en IIS. Para los servicios autohospedados, debe modificar el archivo por lotes para almacenar el certificado de servidor en la ubicación del almacén CurrentUser, reemplazando la cadena 'LocalMachine' con 'CurrentUser'.
echo ************
echo Server cert setup starting
echo %SERVER_NAME%
echo ************
echo making server cert
echo ************
makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
Instalación del certificado de servidor en el almacén de certificados de confianza del cliente
Las líneas siguientes del archivo por lotes Setup.bat copian el certificado de servidor en el almacén de los usuarios de confianza del cliente. Este paso es necesario porque el sistema cliente no confía implícitamente en los certificados generados por Makecert.exe. Si ya tiene un certificado que se basa en un certificado raíz de confianza de cliente (por ejemplo, un certificado emitido por Microsoft), este paso para rellenar el almacén de certificados de cliente con el certificado de servidor no es necesario.
echo ************
echo copying server cert to client's TrustedPeople store
echo ************certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
Habilitación del acceso a la clave privada del certificado
Para habilitar el acceso a la clave privada del certificado desde el servicio hospedado en IIS, la cuenta de usuario en la que se ejecuta el proceso hospedado en IIS debe concederse los permisos adecuados para la clave privada. Esto se logra con los últimos pasos del script Setup.bat.
echo ************
echo setting privileges on server certificates
echo ************
for /F "delims=" %%i in ('"%ProgramFiles%\ServiceModelSampleTools\FindPrivateKey.exe" My LocalMachine -n CN^=%SERVER_NAME% -a') do set PRIVATE_KEY_FILE=%%i
set WP_ACCOUNT=NT AUTHORITY\NETWORK SERVICE
(ver | findstr /C:"5.1") && set WP_ACCOUNT=%COMPUTERNAME%\ASPNET
echo Y|cacls.exe "%PRIVATE_KEY_FILE%" /E /G "%WP_ACCOUNT%":R
iisreset
Para configurar, compilar y ejecutar el ejemplo
Asegúrese de haber realizado el procedimiento de configuraciónOne-Time para las muestras de Windows Communication Foundation.
Para compilar la solución, siga las instrucciones que se indican en Compilación de los ejemplos de Windows Communication Foundation.
Para ejecutar el ejemplo en una configuración de una máquina única o entre máquinas, siga estas instrucciones.
Para ejecutar el ejemplo en la misma máquina
Ejecute el archivo Setup.bat de la carpeta de instalación del ejemplo dentro de un Símbolo del sistema de Visual Studio con privilegios de administrador. Esto instala todos los certificados necesarios para ejecutar el ejemplo.
Nota:
El archivo por lotes Setup.bat está diseñado para ejecutarse desde el símbolo del sistema de Visual Studio. La variable de entorno PATH que se establece en el símbolo del sistema de Visual Studio señala al directorio que contiene los archivos ejecutables que requiere el script Setup.bat. Asegúrese de quitar los certificados ejecutando Cleanup.bat cuando termine con el ejemplo. Otros ejemplos de seguridad usan los mismos certificados.
Inicie Client.exe desde \client\bin. La actividad de cliente se muestra en la aplicación de consola cliente.
Si el cliente y el servicio no pueden comunicarse, consulte Sugerencias de solución de problemas para ejemplos de WCF.
Para ejecutar el ejemplo en varias máquinas
Cree un directorio en el equipo de servicio. Cree una aplicación virtual denominada servicemodelsamples para este directorio mediante la herramienta de administración de Internet Information Services (IIS).
Copie los archivos de programa de servicio de \inetpub\wwwroot\servicemodelsamples en el directorio virtual de la máquina de servicio. Asegúrese de copiar los archivos en el subdirectorio \bin. Copie también los archivos Setup.bat, Cleanup.baty ImportClientCert.bat en la máquina de servicio.
Cree un directorio en el equipo cliente para los archivos binarios de cliente.
Copie los archivos de programa cliente en el directorio de cliente en el equipo cliente. Copie también los archivos Setup.bat, Cleanup.baty ImportServiceCert.bat en el cliente.
En el servidor, ejecute
setup.bat serviceen un Símbolo del sistema para desarrolladores de Visual Studio con privilegios de administrador. La ejecuciónsetup.batcon elserviceargumento crea un certificado de servicio con el nombre de dominio completo de la máquina y exporta el certificado de servicio a un archivo denominado Service.cer.Edite Web.config para reflejar el nuevo nombre de certificado (en el
findValueatributo de <serviceCertificate>), que es el mismo que el nombre de dominio completo de la máquina.Copie el archivo Service.cer del directorio de servicio en el directorio cliente del equipo cliente.
En el servidor, ejecute
setup.bat clienten el Símbolo del sistema para desarrolladores de Visual Studio con privilegios de administrador. Al ejecutarsetup.batcon el argumentoclient, se crea un certificado de cliente denominado client.com y se exporta el certificado de cliente a un archivo denominado Client.cer.En el archivo Client.exe.config del equipo cliente, cambie el valor de dirección del punto de conexión para que coincida con la nueva dirección del servicio. Para ello, reemplace localhost por el nombre de dominio completo del servidor.
Copie el archivo Client.cer del directorio cliente en el directorio de servicio del servidor.
En el cliente, ejecute ImportServiceCert.bat. Así se importa el certificado del servicio del archivo Service.cer en el almacén CurrentUser - TrustedPeople.
En el servidor, ejecute ImportClientCert.bat; esto importará el certificado de cliente del archivo Client.cer en el almacén LocalMachine - TrustedPeople.
En el equipo cliente, inicie Client.exe desde una ventana de comandos. Si el cliente y el servicio no pueden comunicarse, consulte Sugerencias de solución de problemas para ejemplos de WCF.
Para limpiar después de la muestra.
- Ejecute Cleanup.bat en la carpeta samples una vez que haya terminado de ejecutar el ejemplo.
Nota:
Este script no quita certificados de servicio en un cliente al ejecutar este ejemplo entre máquinas. Si ha ejecutado ejemplos de WCF que usan certificados entre máquinas, asegúrese de borrar los certificados de servicio que se han instalado en el almacén CurrentUser - TrustedPeople. Para ello, use el siguiente comando: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Por ejemplo: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.