Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W przykładzie Tokeny pomocnicze pokazano, jak dodać dodatkowe tokeny do komunikatu korzystającego z usługi WS-Security. W przykładzie dodano binarny token zabezpieczający X.509 oprócz tokenu zabezpieczającego nazwy użytkownika. Token jest przekazywany w nagłówku komunikatu WS-Security od klienta do usługi, a część komunikatu jest podpisana przy użyciu klucza prywatnego skojarzonego z tokenem zabezpieczającym X.509, aby udowodnić posiadanie certyfikatu X.509 odbiorcy. Jest to przydatne w przypadku, gdy istnieje wymóg posiadania wielu oświadczeń skojarzonych z komunikatem w celu uwierzytelnienia lub autoryzowania nadawcy. Usługa implementuje kontrakt, który definiuje wzorzec komunikacji typu żądanie-odpowiedź.
Demonstruje
W przykładzie pokazano:
Jak klient może przekazać dodatkowe tokeny zabezpieczające do usługi.
Jak serwer może uzyskiwać dostęp do oświadczeń skojarzonych z dodatkowymi tokenami zabezpieczającymi.
Sposób użycia certyfikatu X.509 serwera do ochrony klucza symetrycznego używanego do szyfrowania i podpisu komunikatów.
Uwaga / Notatka
Procedura instalacji i instrukcje kompilacji dla tego przykładu znajdują się na końcu tego tematu.
Klient uwierzytelnia się przy użyciu tokenu nazwy użytkownika i pomocniczego tokenu zabezpieczającego X.509
Usługa udostępnia pojedynczy punkt końcowy do komunikacji, który jest programowo tworzony przy użyciu klas BindingHelper
i EchoServiceHost
. Punkt końcowy składa się z adresu, powiązania i kontraktu. Powiązanie jest konfigurowane przy użyciu wiązań niestandardowych z wykorzystaniem SymmetricSecurityBindingElement
i HttpTransportBindingElement
. W tym przykładzie SymmetricSecurityBindingElement
skonfigurowano do użycia certyfikatu X.509 usługi w celu ochrony klucza symetrycznego podczas transmisji, a także do przekazania UserNameToken
wraz z obsługującym X509SecurityToken
w nagłówku komunikatu WS-Security. Klucz symetryczny służy do szyfrowania treści komunikatu i tokenu zabezpieczającego nazwy użytkownika. Token pomocniczy jest przekazywany jako dodatkowy binarny token zabezpieczający w nagłówku komunikatu WS-Security. Autentyczność tokenu pomocniczego jest udowodniona przez podpisanie części komunikatu przy użyciu klucza prywatnego skojarzonego z pomocniczym tokenem zabezpieczającym X.509.
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);
}
Zachowanie określa poświadczenia używane przez usługę do uwierzytelniania klienta, a także informacje o certyfikacie X.509 tej usługi. W przykładzie użyto nazwy podmiotu CN=localhost
w certyfikacie X.509 usługi.
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();
}
Kod usługi:
[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;
}
}
}
}
}
Punkt końcowy klienta jest skonfigurowany w podobny sposób do punktu końcowego usługi. Klient używa tej samej BindingHelper
klasy do utworzenia powiązania. Pozostała część konfiguracji znajduje się w Client
klasie . Klient konfiguruje informacje o tokenie zabezpieczającym nazwę użytkownika, pomocniczym tokenie zabezpieczającym X.509 oraz informacje o certyfikacie X.509 usługi poprzez kod konfiguracyjny kolekcji zachowań punktu końcowego klienta.
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();
}
Wyświetlanie informacji o dzwoniących
Aby wyświetlić informacje elementu wywołującego, możesz użyć elementu ServiceSecurityContext.Current.AuthorizationContext.ClaimSets
, jak pokazano w poniższym kodzie. Zawiera ServiceSecurityContext.Current.AuthorizationContext.ClaimSets
roszczenia autoryzacyjne skojarzone z bieżącym użytkownikiem. Oświadczenia te są dostarczane automatycznie przez program Windows Communication Foundation (WCF) dla każdego tokenu otrzymanego w komunikacie.
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;
}
}
}
}
Uruchamianie przykładu
Po uruchomieniu przykładu klient najpierw wyświetli monit o podanie nazwy użytkownika i hasła dla tokenu nazwy użytkownika. Upewnij się, że podajesz poprawne wartości dla konta systemowego, ponieważ usługa WCF przypisuje wartości podane w tokenie nazwy użytkownika do tożsamości określonej przez system. Następnie klient wyświetla odpowiedź z usługi. Naciśnij ENTER w oknie klienta, aby zamknąć klienta.
Skonfiguruj plik wsadowy
Plik wsadowy Setup.bat dołączony do tego przykładu umożliwia skonfigurowanie serwera z odpowiednimi certyfikatami w celu uruchamiania aplikacji hostowanej przez usługi Internet Information Services (IIS), która wymaga zabezpieczeń opartych na certyfikatach serwera. Ten plik wsadowy musi zostać zmodyfikowany, aby działać na różnych maszynach lub w przypadku samodzielnego działania.
Poniżej przedstawiono krótkie omówienie różnych sekcji plików wsadowych, dzięki czemu można je zmodyfikować w celu uruchomienia w odpowiedniej konfiguracji.
Tworzenie certyfikatu klienta
Następujące wiersze z pliku wsadowego Setup.bat tworzą certyfikat klienta do użycia. Zmienna %CLIENT_NAME%
określa temat certyfikatu klienta. W tym przykładzie jako nazwę podmiotu użyto "client.com".
Certyfikat jest przechowywany w magazynie Moje (osobiste) w CurrentUser
lokalizacji magazynu.
echo ************
echo making client cert
echo ************
makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe
Instalowanie certyfikatu klienta w zaufanym magazynie serwera
Następujący wiersz w pliku wsadowym Setup.bat kopiuje certyfikat klienta do magazynu zaufanych osób serwera. Ten krok jest wymagany, ponieważ certyfikaty generowane przez Makecert.exe nie są domyślnie zaufane przez system serwera. Jeśli masz już certyfikat osadzony w zaufanym certyfikacie głównym klienta — na przykład certyfikat wystawiony przez firmę Microsoft — ten krok dodawania certyfikatu serwera do magazynu certyfikatów klienta nie jest wymagany.
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
Tworzenie certyfikatu serwera
Następujące wiersze z pliku wsadowego Setup.bat tworzą certyfikat serwera do użycia. Zmienna %SERVER_NAME%
określa nazwę serwera. Zmień tę zmienną, aby określić własną nazwę serwera. Wartość domyślna w tym pliku wsadowym to localhost.
Certyfikat jest przechowywany w magazynie My (Personal) w lokalizacji magazynu LocalMachine. Certyfikat jest przechowywany w magazynie LocalMachine dla usług hostowanych w IIS. W przypadku usług hostowanych samodzielnie należy zmodyfikować plik wsadowy tak, aby przechowywał certyfikat serwera w lokalizacji magazynu CurrentUser, zastępując ciąg LocalMachine na 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
Instalowanie certyfikatu serwera w zaufanym magazynie certyfikatów klienta
Następujące wiersze w pliku wsadowym Setup.bat kopiują certyfikat serwera do magazynu zaufanych certyfikatów klienta. Ten krok jest wymagany, ponieważ certyfikaty generowane przez Makecert.exe nie są automatycznie uznawane za zaufane przez system klienta. Jeśli masz już certyfikat osadzony w zaufanym certyfikacie głównym klienta — na przykład certyfikat wystawiony przez firmę Microsoft — ten krok dodawania certyfikatu serwera do magazynu certyfikatów klienta nie jest wymagany.
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
Włączanie dostępu do klucza prywatnego certyfikatu
Aby umożliwić dostęp do klucza prywatnego certyfikatu z usługi hostowanej przez usługi IIS, konto użytkownika, w ramach którego działa proces hostowany przez usługi IIS, musi mieć odpowiednie uprawnienia dla klucza prywatnego. Jest to realizowane przez ostatnie kroki skryptu 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
Aby skonfigurować, skompilować i uruchomić przykładowy program
Upewnij się, że wykonano procedurę konfiguracjiOne-Time dla próbek Windows Communication Foundation.
Aby skompilować rozwiązanie, postępuj zgodnie z instrukcjami w temacie Building the Windows Communication Foundation Samples (Tworzenie przykładów programu Windows Communication Foundation).
Aby uruchomić przykład w konfiguracji pojedynczej lub między maszynami, skorzystaj z poniższych instrukcji.
Aby uruchomić przykład na tej samej maszynie
Uruchom Setup.bat z folderu przykładowej instalacji w wierszu polecenia Visual Studio uruchomionym z uprawnieniami administratora. To instaluje wszystkie certyfikaty wymagane do uruchomienia próbki.
Uwaga / Notatka
Plik wsadowy Setup.bat jest przeznaczony do uruchamiania z poziomu wiersza polecenia programu Visual Studio. Zmienna środowiskowa PATH ustawiona w wierszu polecenia programu Visual Studio wskazuje katalog zawierający pliki wykonywalne wymagane przez skrypt Setup.bat. Pamiętaj, aby usunąć certyfikaty, uruchamiając Cleanup.bat po zakończeniu pracy z próbką. Inne przykłady zabezpieczeń używają tych samych certyfikatów.
Uruchom Client.exe z \client\bin. Działanie klienta jest wyświetlane w aplikacji konsolowej klienta.
Jeśli klient i usługa nie mogą się komunikować, sprawdź sekcję „Wskazówki dotyczące rozwiązywania problemów z przykładami programu WCF”.
Aby uruchomić program próbny na różnych maszynach
Utwórz katalog na maszynie usługi. Utwórz aplikację wirtualną o nazwie servicemodelsamples dla tego katalogu przy użyciu narzędzia do zarządzania usługami Internet Information Services (IIS).
Skopiuj pliki programu usługi z folderu \inetpub\wwwroot\servicemodelsamples do katalogu wirtualnego na maszynie usługi. Upewnij się, że skopiujesz pliki w podkatalogu \bin. Skopiuj również pliki Setup.bat, Cleanup.bati ImportClientCert.bat na maszynę serwisową.
Utwórz katalog na maszynie klienckiej dla plików binarnych klienta.
Skopiuj pliki programu klienckiego do katalogu klienta na komputerze klienckim. Skopiuj również pliki Setup.bat, Cleanup.bati ImportServiceCert.bat do klienta.
Na serwerze uruchom polecenie
setup.bat service
w wierszu polecenia dla deweloperów dla programu Visual Studio otwarte z uprawnieniami administratora. Uruchomieniesetup.bat
z argumentemservice
powoduje utworzenie certyfikatu usługi z w pełni kwalifikowaną nazwą domeny maszyny i eksportuje certyfikat usługi do pliku o nazwie Service.cer.Edytuj Web.config, aby odzwierciedlić nową nazwę certyfikatu (w atrybucie
findValue
w <serviceCertificate>), która jest taka sama jak pełna kwalifikowana nazwa domeny maszyny.Skopiuj plik Service.cer z katalogu usługi do katalogu klienta na komputerze klienckim.
Na kliencie uruchom polecenie
setup.bat client
w wierszu polecenia dla deweloperów dla programu Visual Studio otwarte z uprawnieniami administratora. Uruchomieniesetup.bat
z argumentemclient
tworzy certyfikat klienta o nazwie client.com i eksportuje certyfikat klienta do pliku o nazwie Client.cer.W pliku Client.exe.config na komputerze klienckim zmień wartość adresu punktu końcowego, aby odpowiadała nowemu adresowi usługi. W tym celu należy zastąpić hosta lokalnego w pełni kwalifikowaną nazwą domeny serwera.
Skopiuj plik Client.cer z katalogu klienta do katalogu usługi na serwerze.
Na kliencie uruchom polecenie ImportServiceCert.bat. Spowoduje to zaimportowanie certyfikatu usługi z pliku Service.cer do magazynu CurrentUser - TrustedPeople.
Na serwerze uruchom polecenie ImportClientCert.bat. Spowoduje to zaimportowanie certyfikatu klienta z pliku Client.cer do magazynu LocalMachine — TrustedPeople.
Na komputerze klienckim uruchom Client.exe w oknie wiersza polecenia. Jeśli klient i usługa nie mogą się komunikować, sprawdź sekcję „Wskazówki dotyczące rozwiązywania problemów z przykładami programu WCF”.
Aby posprzątać po próbie
- Uruchom Cleanup.bat w folderze próbek po zakończeniu uruchamiania próbki.
Uwaga / Notatka
Ten skrypt nie usuwa certyfikatów usługi na kliencie podczas uruchamiania tego przykładu na maszynach. Jeśli uruchomiłeś przykłady programu WCF, które korzystają z certyfikatów na różnych maszynach, upewnij się, że wyczyścisz certyfikaty usługi zainstalowane w magazynie bieżącego użytkownika CurrentUser - TrustedPeople. W tym celu użyj następującego polecenia: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name>
Na przykład: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com
.