Anvisningar: Ändra kryptografiprovidern för ett X.509-certifikats privata nyckel
Det här avsnittet visar hur du ändrar den kryptografiska provider som används för att tillhandahålla ett X.509-certifikats privata nyckel och hur du integrerar providern i Säkerhetsramverket för Windows Communication Foundation (WCF). Mer information om hur du använder certifikat finns i Arbeta med certifikat.
WCF-säkerhetsramverket är ett sätt att introducera nya typer av säkerhetstoken enligt beskrivningen i Så här skapar du en anpassad token. Du kan också använda en anpassad token för att ersätta befintliga tokentyper som tillhandahålls av systemet.
I det här avsnittet ersätts den systembaserade X.509-säkerhetstoken med en anpassad X.509-token som tillhandahåller en annan implementering för den privata certifikatnyckeln. Detta är användbart i scenarier där den faktiska privata nyckeln tillhandahålls av en annan kryptografisk provider än standardleverantören för Windows-kryptografi. Ett exempel på en alternativ kryptografiprovider är en maskinvarusäkerhetsmodul som utför alla kryptografiska åtgärder relaterade till privata nycklar och inte lagrar de privata nycklarna i minnet, vilket förbättrar säkerheten i systemet.
Följande exempel är endast i demonstrationssyfte. Den ersätter inte standardkryptografiprovidern för Windows, men den visar var en sådan provider kan integreras.
Förfaranden
Varje säkerhetstoken som har en associerad säkerhetsnyckel eller nycklar måste implementera SecurityKeys egenskapen, som returnerar en samling nycklar från säkerhetstokeninstansen. Om token är en X.509-säkerhetstoken innehåller samlingen en enda instans av X509AsymmetricSecurityKey klassen som representerar både offentliga och privata nycklar som är associerade med certifikatet. Om du vill ersätta standardkryptografiprovidern som används för att tillhandahålla certifikatets nycklar skapar du en ny implementering av den här klassen.
Skapa en anpassad Asymmetrisk X.509-nyckel
Definiera en ny klass som härleds X509AsymmetricSecurityKey från klassen.
Åsidosätt den KeySize skrivskyddade egenskapen. Den här egenskapen returnerar den faktiska nyckelstorleken för certifikatets offentliga/privata nyckelpar.
Åsidosätt DecryptKey metoden. Den här metoden anropas av WCF-säkerhetsramverket för att dekryptera en symmetrisk nyckel med certifikatets privata nyckel. (Nyckeln har tidigare krypterats med certifikatets offentliga nyckel.)
Åsidosätt GetAsymmetricAlgorithm metoden. Den här metoden anropas av WCF-säkerhetsramverket för att hämta en instans av AsymmetricAlgorithm klassen som representerar kryptografiprovidern för antingen certifikatets privata eller offentliga nyckel, beroende på vilka parametrar som skickas till metoden.
Valfritt. Åsidosätt GetHashAlgorithmForSignature metoden. Åsidosätt den här metoden om en annan implementering av HashAlgorithm klassen krävs.
Åsidosätt GetSignatureFormatter metoden. Den här metoden returnerar en instans av AsymmetricSignatureFormatter klassen som är associerad med certifikatets privata nyckel.
Åsidosätt IsSupportedAlgorithm metoden. Den här metoden används för att ange om en viss kryptografisk algoritm stöds av implementeringen av säkerhetsnyckeln.
class CustomX509AsymmetricSecurityKey : X509AsymmetricSecurityKey { X509Certificate2 certificate; object thisLock = new Object(); bool privateKeyAvailabilityDetermined; AsymmetricAlgorithm privateKey; PublicKey publicKey; public CustomX509AsymmetricSecurityKey(X509Certificate2 certificate) : base(certificate) { this.certificate = certificate; } public override int KeySize { get { return this.PublicKey.Key.KeySize; } } AsymmetricAlgorithm PrivateKey { // You need to modify this to obtain the private key using a different cryptographic // provider if you do not want to use the default provider. get { if (!this.privateKeyAvailabilityDetermined) { lock (ThisLock) { if (!this.privateKeyAvailabilityDetermined) { this.privateKey = this.certificate.PrivateKey; this.privateKeyAvailabilityDetermined = true; } } } return this.privateKey; } } PublicKey PublicKey { get { if (this.publicKey == null) { lock (ThisLock) { this.publicKey ??= this.certificate.PublicKey; } } return this.publicKey; } } Object ThisLock { get { return thisLock; } } public override byte[] DecryptKey(string algorithm, byte[] keyData) { // You can decrypt the key only if you have the private key in the certificate. if (this.PrivateKey == null) { throw new NotSupportedException("Missing private key"); } RSA rsa = this.PrivateKey as RSA; if (rsa == null) { throw new NotSupportedException("Private key cannot be used with RSA algorithm"); } // Support exchange keySpec, AT_EXCHANGE ? if (rsa.KeyExchangeAlgorithm == null) { throw new NotSupportedException("Private key does not support key exchange"); } switch (algorithm) { case EncryptedXml.XmlEncRSA15Url: return EncryptedXml.DecryptKey(keyData, rsa, false); case EncryptedXml.XmlEncRSAOAEPUrl: return EncryptedXml.DecryptKey(keyData, rsa, true); default: throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } } public override AsymmetricAlgorithm GetAsymmetricAlgorithm(string algorithm, bool privateKey) { if (privateKey) { if (this.PrivateKey == null) { throw new NotSupportedException("Missing private key"); } switch (algorithm) { case SignedXml.XmlDsigDSAUrl: if ((this.PrivateKey as DSA) != null) { return (this.PrivateKey as DSA); } throw new NotSupportedException("Private key cannot be used with DSA"); case SignedXml.XmlDsigRSASHA1Url: case EncryptedXml.XmlEncRSA15Url: case EncryptedXml.XmlEncRSAOAEPUrl: if ((this.PrivateKey as RSA) != null) { return (this.PrivateKey as RSA); } throw new NotSupportedException("Private key cannot be used with RSA"); default: throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } } else { switch (algorithm) { case SignedXml.XmlDsigDSAUrl: if ((this.PublicKey.Key as DSA) != null) { return (this.PublicKey.Key as DSA); } throw new NotSupportedException("Public key cannot be used with DSA"); case SignedXml.XmlDsigRSASHA1Url: case EncryptedXml.XmlEncRSA15Url: case EncryptedXml.XmlEncRSAOAEPUrl: if ((this.PublicKey.Key as RSA) != null) { return (this.PublicKey.Key as RSA); } throw new NotSupportedException("Public key cannot be used with RSA"); default: throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } } } public override HashAlgorithm GetHashAlgorithmForSignature(string algorithm) { if (!this.IsSupportedAlgorithm(algorithm)) { throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } switch (algorithm) { case SignedXml.XmlDsigDSAUrl: case SignedXml.XmlDsigRSASHA1Url: return new SHA1Managed(); default: throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } } public override AsymmetricSignatureFormatter GetSignatureFormatter(string algorithm) { // The signature can be created only if the private key is present. if (this.PrivateKey == null) { throw new NotSupportedException("Private key is missing"); } // Only one of the two algorithms is supported, not both. // XmlDsigDSAUrl = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; // XmlDsigRSASHA1Url = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; switch (algorithm) { case SignedXml.XmlDsigDSAUrl: // Ensure that this is a DSA algorithm object. DSA dsa = (this.PrivateKey as DSA); if (dsa == null) { throw new NotSupportedException("Private key cannot be used DSA"); } return new DSASignatureFormatter(dsa); case SignedXml.XmlDsigRSASHA1Url: // Ensure that this is an RSA algorithm object. RSA rsa = (this.PrivateKey as RSA); if (rsa == null) { throw new NotSupportedException("Private key cannot be used RSA"); } return new RSAPKCS1SignatureFormatter(rsa); default: throw new NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)); } } public override bool IsSupportedAlgorithm(string algorithm) { switch (algorithm) { case SignedXml.XmlDsigDSAUrl: return (this.PublicKey.Key is DSA); case SignedXml.XmlDsigRSASHA1Url: case EncryptedXml.XmlEncRSA15Url: case EncryptedXml.XmlEncRSAOAEPUrl: return (this.PublicKey.Key is RSA); default: return false; } } }
Friend Class CustomX509AsymmetricSecurityKey Inherits X509AsymmetricSecurityKey Private _certificate As X509Certificate2 Private _thisLock As New Object() Private _privateKeyAvailabilityDetermined As Boolean Private _privateKey As AsymmetricAlgorithm Private _publicKey As PublicKey Public Sub New(ByVal certificate As X509Certificate2) MyBase.New(certificate) Me._certificate = certificate End Sub Public Overrides ReadOnly Property KeySize() As Integer Get Return Me.PublicKey.Key.KeySize End Get End Property Private Overloads ReadOnly Property PrivateKey() As AsymmetricAlgorithm ' You need to modify this to obtain the private key using a different cryptographic ' provider if you do not want to use the default provider. Get If Not Me._privateKeyAvailabilityDetermined Then SyncLock ThisLock If Not Me._privateKeyAvailabilityDetermined Then Me._privateKey = Me._certificate.PrivateKey Me._privateKeyAvailabilityDetermined = True End If End SyncLock End If Return Me._privateKey End Get End Property Private Overloads ReadOnly Property PublicKey() As PublicKey Get If Me._publicKey Is Nothing Then SyncLock ThisLock If Me._publicKey Is Nothing Then Me._publicKey = Me._certificate.PublicKey End If End SyncLock End If Return Me._publicKey End Get End Property Private Overloads ReadOnly Property ThisLock() As Object Get Return _thisLock End Get End Property Public Overrides Function DecryptKey(ByVal algorithm As String, _ ByVal keyData() As Byte) As Byte() ' You can decrypt the key only if you have the private key in the certificate. If Me.PrivateKey Is Nothing Then Throw New NotSupportedException("Missing private key") End If Dim rsa = TryCast(Me.PrivateKey, RSA) If rsa Is Nothing Then Throw New NotSupportedException("Private key cannot be used with RSA algorithm") End If ' Support exchange keySpec, AT_EXCHANGE ? If rsa.KeyExchangeAlgorithm Is Nothing Then Throw New NotSupportedException("Private key does not support key exchange") End If Select Case algorithm Case EncryptedXml.XmlEncRSA15Url Return EncryptedXml.DecryptKey(keyData, rsa, False) Case EncryptedXml.XmlEncRSAOAEPUrl Return EncryptedXml.DecryptKey(keyData, rsa, True) Case Else Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End Select End Function Public Overrides Function GetAsymmetricAlgorithm(ByVal algorithm As String, _ ByVal privateKey As Boolean) As AsymmetricAlgorithm If privateKey Then If Me.PrivateKey Is Nothing Then Throw New NotSupportedException("Missing private key") End If Select Case algorithm Case SignedXml.XmlDsigDSAUrl If TryCast(Me.PrivateKey, DSA) IsNot Nothing Then Return (TryCast(Me.PrivateKey, DSA)) End If Throw New NotSupportedException("Private key cannot be used with DSA") Case SignedXml.XmlDsigRSASHA1Url, EncryptedXml.XmlEncRSA15Url, EncryptedXml.XmlEncRSAOAEPUrl If TryCast(Me.PrivateKey, RSA) IsNot Nothing Then Return (TryCast(Me.PrivateKey, RSA)) End If Throw New NotSupportedException("Private key cannot be used with RSA") Case Else Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End Select Else Select Case algorithm Case SignedXml.XmlDsigDSAUrl If TryCast(Me.PublicKey.Key, DSA) IsNot Nothing Then Return (TryCast(Me.PublicKey.Key, DSA)) End If Throw New NotSupportedException("Public key cannot be used with DSA") Case SignedXml.XmlDsigRSASHA1Url, EncryptedXml.XmlEncRSA15Url, EncryptedXml.XmlEncRSAOAEPUrl If TryCast(Me.PublicKey.Key, RSA) IsNot Nothing Then Return (TryCast(Me.PublicKey.Key, RSA)) End If Throw New NotSupportedException("Public key cannot be used with RSA") Case Else Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End Select End If End Function Public Overrides Function GetHashAlgorithmForSignature(ByVal algorithm As String) As HashAlgorithm If Not Me.IsSupportedAlgorithm(algorithm) Then Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End If Select Case algorithm Case SignedXml.XmlDsigDSAUrl, SignedXml.XmlDsigRSASHA1Url Return New SHA1Managed() Case Else Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End Select End Function Public Overrides Function GetSignatureFormatter(ByVal algorithm As String) As AsymmetricSignatureFormatter ' The signature can be created only if the private key is present. If Me.PrivateKey Is Nothing Then Throw New NotSupportedException("Private key is missing") End If ' Only one of the two algorithms is supported, not both. ' XmlDsigDSAUrl = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; ' XmlDsigRSASHA1Url = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; Select Case algorithm Case SignedXml.XmlDsigDSAUrl ' Ensure that this is a DSA algorithm object. Dim dsa = (TryCast(Me.PrivateKey, DSA)) If dsa Is Nothing Then Throw New NotSupportedException("Private key cannot be used DSA") End If Return New DSASignatureFormatter(dsa) Case SignedXml.XmlDsigRSASHA1Url ' Ensure that this is an RSA algorithm object. Dim rsa = (TryCast(Me.PrivateKey, RSA)) If rsa Is Nothing Then Throw New NotSupportedException("Private key cannot be used RSA") End If Return New RSAPKCS1SignatureFormatter(rsa) Case Else Throw New NotSupportedException(String.Format("Algorithm {0} is not supported", algorithm)) End Select End Function Public Overrides Function IsSupportedAlgorithm(ByVal algorithm As String) As Boolean Select Case algorithm Case SignedXml.XmlDsigDSAUrl Return (TypeOf Me.PublicKey.Key Is DSA) Case SignedXml.XmlDsigRSASHA1Url, EncryptedXml.XmlEncRSA15Url, EncryptedXml.XmlEncRSAOAEPUrl Return (TypeOf Me.PublicKey.Key Is RSA) Case Else Return False End Select End Function End Class
Följande procedur visar hur du integrerar den anpassade X.509-asymmetriska säkerhetsnyckelimplementeringen som skapades i föregående procedur med WCF-säkerhetsramverket för att ersätta den systemspecifika X.509-säkerhetstoken.
Så här ersätter du den systemspecifika X.509-säkerhetstoken med en anpassad Asymmetrisk X.509-säkerhetsnyckeltoken
Skapa en anpassad X.509-säkerhetstoken som returnerar den anpassade asymmetriska X.509-säkerhetsnyckeln i stället för den systemspecifika säkerhetsnyckeln, som du ser i följande exempel. Mer information om anpassade säkerhetstoken finns i Så här skapar du en anpassad token.
class CustomX509SecurityToken : X509SecurityToken { ReadOnlyCollection<SecurityKey> securityKeys; public CustomX509SecurityToken(X509Certificate2 certificate) : base(certificate) { } public override ReadOnlyCollection<SecurityKey> SecurityKeys { get { if (this.securityKeys == null) { List<SecurityKey> temp = new List<SecurityKey>(1); temp.Add(new CustomX509AsymmetricSecurityKey(this.Certificate)); this.securityKeys = temp.AsReadOnly(); } return this.securityKeys; } } }
Friend Class CustomX509SecurityToken Inherits X509SecurityToken Private _securityKeys As ReadOnlyCollection(Of SecurityKey) Public Sub New(ByVal certificate As X509Certificate2) MyBase.New(certificate) End Sub Public Overrides ReadOnly Property SecurityKeys() As ReadOnlyCollection(Of SecurityKey) Get If Me._securityKeys Is Nothing Then Dim temp As New List(Of SecurityKey)(1) temp.Add(New CustomX509AsymmetricSecurityKey(Me.Certificate)) Me._securityKeys = temp.AsReadOnly() End If Return Me._securityKeys End Get End Property End Class
Skapa en anpassad säkerhetstokenprovider som returnerar en anpassad X.509-säkerhetstoken, som du ser i exemplet. Mer information om anpassade leverantörer av säkerhetstoken finns i Så här skapar du en anpassad säkerhetstokenprovider.
class CustomX509SecurityTokenProvider : SecurityTokenProvider { X509Certificate2 certificate; public CustomX509SecurityTokenProvider(X509Certificate2 certificate) { this.certificate = certificate; } protected override SecurityToken GetTokenCore(TimeSpan timeout) { return new CustomX509SecurityToken(certificate); } }
Friend Class CustomX509SecurityTokenProvider Inherits SecurityTokenProvider Private _certificate As X509Certificate2 Public Sub New(ByVal certificate As X509Certificate2) Me._certificate = certificate End Sub Protected Overrides Function GetTokenCore(ByVal timeout As TimeSpan) As SecurityToken Return New CustomX509SecurityToken(_certificate) End Function End Class
Om den anpassade säkerhetsnyckeln måste användas på initierarsidan skapar du en anpassad klientsäkerhetstokenhanterare och anpassade klientautentiseringsuppgifter, som du ser i följande exempel. Mer information om anpassade klientautentiseringsuppgifter och tokenhanterare för klientsäkerhet finns i Genomgång: Skapa anpassade klient- och tjänstautentiseringsuppgifter.
class CustomClientSecurityTokenManager : ClientCredentialsSecurityTokenManager { CustomClientCredentials credentials; public CustomClientSecurityTokenManager(CustomClientCredentials credentials) : base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) { SecurityTokenProvider result = null; if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate) { MessageDirection direction = tokenRequirement.GetProperty<MessageDirection>(ServiceModelSecurityTokenRequirement.MessageDirectionProperty); if (direction == MessageDirection.Output) { if (tokenRequirement.KeyUsage == SecurityKeyUsage.Signature) { result = new CustomX509SecurityTokenProvider(credentials.ClientCertificate.Certificate); } } else { if (tokenRequirement.KeyUsage == SecurityKeyUsage.Exchange) { result = new CustomX509SecurityTokenProvider(credentials.ClientCertificate.Certificate); } } } result ??= base.CreateSecurityTokenProvider(tokenRequirement); return result; } }
Friend Class CustomClientSecurityTokenManager Inherits ClientCredentialsSecurityTokenManager Private credentials As CustomClientCredentials Public Sub New(ByVal credentials As CustomClientCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider(ByVal tokenRequirement As SecurityTokenRequirement) _ As SecurityTokenProvider Dim result As SecurityTokenProvider = Nothing If tokenRequirement.TokenType = SecurityTokenTypes.X509Certificate Then Dim direction = tokenRequirement.GetProperty(Of MessageDirection) _ (ServiceModelSecurityTokenRequirement.MessageDirectionProperty) If direction = MessageDirection.Output Then If tokenRequirement.KeyUsage = SecurityKeyUsage.Signature Then result = New CustomX509SecurityTokenProvider(credentials.ClientCertificate.Certificate) End If Else If tokenRequirement.KeyUsage = SecurityKeyUsage.Exchange Then result = New CustomX509SecurityTokenProvider(credentials.ClientCertificate.Certificate) End If End If End If If result Is Nothing Then result = MyBase.CreateSecurityTokenProvider(tokenRequirement) End If Return result End Function End Class
public class CustomClientCredentials : ClientCredentials { public CustomClientCredentials() { } protected CustomClientCredentials(CustomClientCredentials other) : base(other) { } protected override ClientCredentials CloneCore() { return new CustomClientCredentials(this); } public override SecurityTokenManager CreateSecurityTokenManager() { return new CustomClientSecurityTokenManager(this); } }
Public Class CustomClientCredentials Inherits ClientCredentials Public Sub New() End Sub Protected Sub New(ByVal other As CustomClientCredentials) MyBase.New(other) End Sub Protected Overrides Function CloneCore() As ClientCredentials Return New CustomClientCredentials(Me) End Function Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager Return New CustomClientSecurityTokenManager(Me) End Function End Class
Om den anpassade säkerhetsnyckeln måste användas på mottagarsidan skapar du en anpassad tjänstsäkerhetstokenhanterare och autentiseringsuppgifter för anpassad tjänst, som du ser i följande exempel. Mer information om autentiseringsuppgifter för anpassade tjänster och hanterare av tjänstsäkerhetstoken finns i Genomgång: Skapa autentiseringsuppgifter för anpassad klient och tjänst.
class CustomServiceSecurityTokenManager : ServiceCredentialsSecurityTokenManager { CustomServiceCredentials credentials; public CustomServiceSecurityTokenManager(CustomServiceCredentials credentials) : base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) { SecurityTokenProvider result = null; if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate) { MessageDirection direction = tokenRequirement.GetProperty<MessageDirection>(ServiceModelSecurityTokenRequirement.MessageDirectionProperty); if (direction == MessageDirection.Input) { if (tokenRequirement.KeyUsage == SecurityKeyUsage.Exchange) { result = new CustomX509SecurityTokenProvider(credentials.ServiceCertificate.Certificate); } } else { if (tokenRequirement.KeyUsage == SecurityKeyUsage.Signature) { result = new CustomX509SecurityTokenProvider(credentials.ServiceCertificate.Certificate); } } } result ??= base.CreateSecurityTokenProvider(tokenRequirement); return result; } }
Friend Class CustomServiceSecurityTokenManager Inherits ServiceCredentialsSecurityTokenManager Private credentials As CustomServiceCredentials Public Sub New(ByVal credentials As CustomServiceCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider(ByVal tokenRequirement As SecurityTokenRequirement) As SecurityTokenProvider Dim result As SecurityTokenProvider = Nothing If tokenRequirement.TokenType = SecurityTokenTypes.X509Certificate Then Dim direction = tokenRequirement.GetProperty(Of MessageDirection) _ (ServiceModelSecurityTokenRequirement.MessageDirectionProperty) If direction = MessageDirection.Input Then If tokenRequirement.KeyUsage = SecurityKeyUsage.Exchange Then result = New CustomX509SecurityTokenProvider(credentials.ServiceCertificate.Certificate) End If Else If tokenRequirement.KeyUsage = SecurityKeyUsage.Signature Then result = New CustomX509SecurityTokenProvider(credentials.ServiceCertificate.Certificate) End If End If End If If result Is Nothing Then result = MyBase.CreateSecurityTokenProvider(tokenRequirement) End If Return result End Function End Class
public class CustomServiceCredentials : ServiceCredentials { public CustomServiceCredentials() { } protected CustomServiceCredentials(CustomServiceCredentials other) : base(other) { } protected override ServiceCredentials CloneCore() { return new CustomServiceCredentials(this); } public override SecurityTokenManager CreateSecurityTokenManager() { return new CustomServiceSecurityTokenManager(this); } }
Public Class CustomServiceCredentials Inherits ServiceCredentials Public Sub New() End Sub Protected Sub New(ByVal other As CustomServiceCredentials) MyBase.New(other) End Sub Protected Overrides Function CloneCore() As ServiceCredentials Return New CustomServiceCredentials(Me) End Function Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager Return New CustomServiceSecurityTokenManager(Me) End Function End Class
Se även
- X509AsymmetricSecurityKey
- AsymmetricSecurityKey
- SecurityKey
- AsymmetricAlgorithm
- HashAlgorithm
- AsymmetricSignatureFormatter
- Genomgång: Skapa anpassade klient- och tjänstautentiseringsuppgifter
- Gör så här: Skapa en anpassad säkerhetstokenautentisering
- Gör så här: Skapa en anpassad säkerhetstokenprovider
- Anvisningar: Skapa en anpassad token