Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
U kunt de toegang tot uw Azure App Service-app beperken door verschillende typen verificatie voor de app in te schakelen. Een manier om verificatie in te stellen, is door een clientcertificaat aan te vragen wanneer de clientaanvraag wordt verzonden met behulp van Transport Layer Security (TLS) / SSL (Secure Sockets Layer) en om het certificaat te valideren. Dit mechanisme wordt wederzijdse verificatie of clientcertificaatverificatie genoemd. In dit artikel wordt beschreven hoe u uw app instelt voor het gebruik van clientcertificaatverificatie.
Notitie
Uw app-code moet het clientcertificaat valideren. App Service doet niets met het clientcertificaat behalve het doorsturen naar uw app.
Als u toegang hebt tot uw site via HTTP en niet HTTPS, ontvangt u geen clientcertificaten. Als voor uw toepassing clientcertificaten zijn vereist, moet u aanvragen voor uw toepassing niet via HTTP toestaan.
Uw web-app voorbereiden
Als u aangepaste TLS/SSL-bindingen wilt maken of clientcertificaten wilt inschakelen voor uw App Service-app, moet uw App Service-plan zich in de lagen Basic, Standard, Premium of Isolated bevinden.
Ga als volgt te werk om ervoor te zorgen dat uw web-app zich in een ondersteunde prijscategorie bevindt:
Ga naar uw web-app
Voer In het zoekvak van Azure PortalApp Services in en selecteer deze vervolgens in de zoekresultaten.
Selecteer uw web-app op de pagina App Services :
U bent nu op de beheerpagina van uw web-app.
Controleer de prijscategorie
Selecteer in het linkermenu voor uw web-app onder Instellingen de optie Omhoog schalen (App Service-plan).
Zorg ervoor dat uw web-app zich niet in de F1- of D1-laag bevindt. Deze lagen bieden geen ondersteuning voor aangepaste TLS/SSL.
Als u omhoog moet schalen, volgt u de stappen in het volgende gedeelte. Sluit het deelvenster Opschalen en sla, anders, de volgende sectie over.
Uw App Service-plan omhoog schalen
Selecteer een niet-gratis laag, zoals B1, B2, B3 of een andere laag in de categorie Productie.
Wanneer u klaar bent, kiest u Selecteren.
Wanneer de schaalbewerking voltooid is, ziet u een bericht dat het plan is bijgewerkt.
Clientcertificaten inschakelen
Wanneer u clientcertificaten voor uw app inschakelt, moet u de gewenste clientcertificaatmodus selecteren. In de modus wordt gedefinieerd hoe uw app binnenkomende clientcertificaten verwerkt. De modi worden beschreven in de volgende tabel:
Klantcertificaatmodus | Beschrijving |
---|---|
Vereist | Voor alle aanvragen is een clientcertificaat vereist. |
Optioneel | Aanvragen kunnen gebruikmaken van een clientcertificaat. Clients worden standaard om een certificaat gevraagd. Browserclients geven bijvoorbeeld een prompt weer om een certificaat voor verificatie te selecteren. |
Optionele interactieve gebruiker | Aanvragen kunnen gebruikmaken van een clientcertificaat. Clients worden niet standaard om een certificaat gevraagd. Browserclients geven bijvoorbeeld geen prompt weer om een certificaat voor verificatie te selecteren. |
De Azure-portal gebruiken om clientcertificaten in te schakelen:
- Ga naar de pagina voor app-beheer.
- Selecteer inhet linkermenu> instellingen.
- Selecteer uw keuze voor de clientcertificaatmodus.
- Selecteer Opslaan.
Paden uitsluiten van de plicht tot authenticatie.
Wanneer u wederzijdse verificatie voor uw toepassing inschakelt, hebben alle paden onder de hoofdmap van uw app een clientcertificaat nodig voor toegang. Als u deze vereiste voor bepaalde paden wilt verwijderen, definieert u uitsluitingspaden als onderdeel van uw toepassingsconfiguratie.
Notitie
Als u een uitsluitingspad voor clientcertificaten gebruikt, wordt TLS-heronderhandeling geactiveerd voor binnenkomende aanvragen naar de app.
Selecteer Instellingenconfiguratie> in het linkermenu van de pagina App-beheer. Selecteer het tabblad Algemene instellingen.
Selecteer naast uitsluitingspaden voor certificaten het potloodpictogram.
Selecteer Nieuw pad, geef een pad of een lijst met paden op, gescheiden door
,
of;
en selecteer vervolgens OK.Selecteer Opslaan.
In de volgende schermopname ziet u hoe u een uitsluitingspad voor certificaten instelt. In dit voorbeeld vraagt elk pad voor de app dat begint met /public
geen clientcertificaat aan. Padkoppeling is niet specifiek.
Clientcertificaat en TLS-heronderhandeling
Voor sommige clientcertificaatinstellingen vereist App Service TLS-heronderhandeling om een aanvraag te lezen voordat u weet of u om een clientcertificaat moet vragen. Beide van de volgende instellingen activeren TLS-heronderhandeling:
- De optionele interactieve gebruikersclientcertificaatmodus gebruiken.
- Een uitsluitingspad voor clientcertificaten gebruiken.
Notitie
TLS 1.3 en HTTP 2.0 bieden geen ondersteuning voor TLS-heronderhandeling. Deze protocollen werken niet als uw app is geconfigureerd met clientcertificaatinstellingen die gebruikmaken van TLS-heronderhandeling.
Als u TLS-heronderhandeling wilt uitschakelen en de app tijdens TLS-handshake wilt laten onderhandelen over clientcertificaten, moet u de volgende acties uitvoeren in uw app:
- Stel het client-certificaat in op modus Vereist of Optioneel.
- Verwijder alle uitsluitingspaden voor clientcertificaten.
Grote bestanden uploaden met TLS-heronderhandeling
Clientcertificaatconfiguraties die gebruikmaken van TLS-heronderhandeling kunnen geen ondersteuning bieden voor binnenkomende aanvragen met bestanden die groter zijn dan 100 kB. Deze limiet wordt veroorzaakt door beperkingen voor buffergrootte. In dit scenario mislukken post- of PUT-aanvragen die meer dan 100 kB zijn met een 403-fout. Deze limiet kan niet worden geconfigureerd en kan niet worden verhoogd.
Als u de limiet van 100 kB wilt aanpakken, kunt u de volgende oplossingen overwegen:
- Schakel TLS-heronderhandeling uit. Voer de volgende acties uit in de clientcertificaatconfiguraties van uw app:
- Stel het client-certificaat in op modus Vereist of Optioneel.
- Verwijder alle uitsluitingspaden voor clientcertificaten.
- Verzend een HEAD-aanvraag vóór de PUT/POST-aanvraag. De HEAD-aanvraag verwerkt het clientcertificaat.
- Voeg de header
Expect: 100-Continue
toe aan uw aanvraag. Deze header zorgt ervoor dat de client wacht totdat de server reageert met een100 Continue
voordat de aanvraagbody wordt verzonden en dat de buffers worden overgeslagen.
Toegang tot het clientcertificaat
In App Service vindt TLS-beëindiging van de aanvraag plaats bij de front-end load balancer. Wanneer App Service de aanvraag doorstuurt naar uw app-code waarvoor clientcertificaten zijn ingeschakeld, wordt er een X-ARR-ClientCert
aanvraagheader met het clientcertificaat geïnjecteerd. App Service doet niets met dit clientcertificaat dan het doorsturen naar uw app. Uw app-code moet het clientcertificaat valideren.
In ASP.NET is het clientcertificaat beschikbaar via de HttpRequest.ClientCertificate
eigenschap.
In andere toepassingsstacks (Node.js, PHP) is het clientcertificaat beschikbaar via een base64-gecodeerde waarde in de X-ARR-ClientCert
aanvraagheader.
ASP.NET Core-voorbeeld
Voor ASP.NET Core is middleware beschikbaar voor het parseren van doorgestuurde certificaten. Afzonderlijke middleware is beschikbaar voor het gebruik van de doorgestuurde protocolheaders. Beide moeten aanwezig zijn om doorgestuurde certificaten te kunnen accepteren. U kunt aangepaste certificaatvalidatielogica plaatsen in de opties voor CertificateAuthentication:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// Configure the application to use the protocol and client IP address forwarded by the front-end load balancer.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// By default, only loopback proxies are allowed. Clear that restriction to enable this explicit configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
// Configure the application to use the client certificate forwarded by the front-end load balancer.
services.AddCertificateForwarding(options => { options.CertificateHeader = "X-ARR-ClientCert"; });
// Add certificate authentication so that when authorization is performed the user will be created from the certificate.
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseForwardedHeaders();
app.UseCertificateForwarding();
app.UseHttpsRedirection();
app.UseAuthentication()
app.UseAuthorization();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
voorbeeld van ASP.NET webformulieren
using System;
using System.Collections.Specialized;
using System.Security.Cryptography.X509Certificates;
using System.Web;
namespace ClientCertificateUsageSample
{
public partial class Cert : System.Web.UI.Page
{
public string certHeader = "";
public string errorString = "";
private X509Certificate2 certificate = null;
public string certThumbprint = "";
public string certSubject = "";
public string certIssuer = "";
public string certSignatureAlg = "";
public string certIssueDate = "";
public string certExpiryDate = "";
public bool isValidCert = false;
//
// Read the certificate from the header into an X509Certificate2 object.
// Display properties of the certificate on the page.
//
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection headers = base.Request.Headers;
certHeader = headers["X-ARR-ClientCert"];
if (!String.IsNullOrEmpty(certHeader))
{
try
{
byte[] clientCertBytes = Convert.FromBase64String(certHeader);
certificate = new X509Certificate2(clientCertBytes);
certSubject = certificate.Subject;
certIssuer = certificate.Issuer;
certThumbprint = certificate.Thumbprint;
certSignatureAlg = certificate.SignatureAlgorithm.FriendlyName;
certIssueDate = certificate.NotBefore.ToShortDateString() + " " + certificate.NotBefore.ToShortTimeString();
certExpiryDate = certificate.NotAfter.ToShortDateString() + " " + certificate.NotAfter.ToShortTimeString();
}
catch (Exception ex)
{
errorString = ex.ToString();
}
finally
{
isValidCert = IsValidClientCertificate();
if (!isValidCert) Response.StatusCode = 403;
else Response.StatusCode = 200;
}
}
else
{
certHeader = "";
}
}
//
// This is a sample verification routine. You should modify this method to suit your application logic and security requirements.
//
//
private bool IsValidClientCertificate()
{
// In this example, the certificate is accepted as a valid certificate only if these conditions are met:
// - The certificate isn't expired and is active for the current time on the server.
// - The subject name of the certificate has the common name nildevecc.
// - The issuer name of the certificate has the common name nildevecc and the organization name Microsoft Corp.
// - The thumbprint of the certificate is 30757A2E831977D8BD9C8496E4C99AB26CB9622B.
//
// This example doesn't test that the certificate is chained to a trusted root authority (or revoked) on the server.
// It allows self-signed certificates.
//
if (certificate == null || !String.IsNullOrEmpty(errorString)) return false;
// 1. Check time validity of the certificate.
if (DateTime.Compare(DateTime.Now, certificate.NotBefore) < 0 || DateTime.Compare(DateTime.Now, certificate.NotAfter) > 0) return false;
// 2. Check the subject name of the certificate.
bool foundSubject = false;
string[] certSubjectData = certificate.Subject.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in certSubjectData)
{
if (String.Compare(s.Trim(), "CN=nildevecc") == 0)
{
foundSubject = true;
break;
}
}
if (!foundSubject) return false;
// 3. Check the issuer name of the certificate.
bool foundIssuerCN = false, foundIssuerO = false;
string[] certIssuerData = certificate.Issuer.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in certIssuerData)
{
if (String.Compare(s.Trim(), "CN=nildevecc") == 0)
{
foundIssuerCN = true;
if (foundIssuerO) break;
}
if (String.Compare(s.Trim(), "O=Microsoft Corp") == 0)
{
foundIssuerO = true;
if (foundIssuerCN) break;
}
}
if (!foundIssuerCN || !foundIssuerO) return false;
// 4. Check the thumbprint of the certificate.
if (String.Compare(certificate.Thumbprint.Trim().ToUpper(), "30757A2E831977D8BD9C8496E4C99AB26CB9622B") != 0) return false;
return true;
}
}
}
voorbeeld van Node.js
De volgende Node.js voorbeeldcode haalt de X-ARR-ClientCert
header op en gebruikt node-forge om de PEM-tekenreeks (Privacy Enhanced Mail) met Base64-codering te converteren naar een certificaatobject en te valideren:
import { NextFunction, Request, Response } from 'express';
import { pki, md, asn1 } from 'node-forge';
export class AuthorizationHandler {
public static authorizeClientCertificate(req: Request, res: Response, next: NextFunction): void {
try {
// Get header.
const header = req.get('X-ARR-ClientCert');
if (!header) throw new Error('UNAUTHORIZED');
// Convert from PEM to PKI certificate.
const pem = `-----BEGIN CERTIFICATE-----${header}-----END CERTIFICATE-----`;
const incomingCert: pki.Certificate = pki.certificateFromPem(pem);
// Validate certificate thumbprint.
const fingerPrint = md.sha1.create().update(asn1.toDer(pki.certificateToAsn1(incomingCert)).getBytes()).digest().toHex();
if (fingerPrint.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
// Validate time validity.
const currentDate = new Date();
if (currentDate < incomingCert.validity.notBefore || currentDate > incomingCert.validity.notAfter) throw new Error('UNAUTHORIZED');
// Validate issuer.
if (incomingCert.issuer.hash.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
// Validate subject.
if (incomingCert.subject.hash.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
next();
} catch (e) {
if (e instanceof Error && e.message === 'UNAUTHORIZED') {
res.status(401).send();
} else {
next(e);
}
}
}
}
Java-voorbeeld
De volgende Java-klasse codeert het certificaat van X-ARR-ClientCert
naar een X509Certificate
exemplaar.
certificateIsValid()
valideert of de vingerafdruk van het certificaat overeenkomt met de vingerafdruk die is opgegeven in de constructor en of het certificaat niet is verlopen.
import java.io.ByteArrayInputStream;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.security.MessageDigest;
import sun.security.provider.X509Factory;
import javax.xml.bind.DatatypeConverter;
import java.util.Base64;
import java.util.Date;
public class ClientCertValidator {
private String thumbprint;
private X509Certificate certificate;
/**
* Constructor.
* @param certificate. The certificate from the "X-ARR-ClientCert" HTTP header.
* @param thumbprint. The thumbprint to check against.
* @throws CertificateException if the certificate factory can't be created.
*/
public ClientCertValidator(String certificate, String thumbprint) throws CertificateException {
certificate = certificate
.replaceAll(X509Factory.BEGIN_CERT, "")
.replaceAll(X509Factory.END_CERT, "");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
byte [] base64Bytes = Base64.getDecoder().decode(certificate);
X509Certificate X509cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(base64Bytes));
this.setCertificate(X509cert);
this.setThumbprint(thumbprint);
}
/**
* Check that the certificate's thumbprint matches the one given in the constructor, and that the
* certificate isn't expired.
* @return True if the certificate's thumbprint matches and isn't expired. False otherwise.
*/
public boolean certificateIsValid() throws NoSuchAlgorithmException, CertificateEncodingException {
return certificateHasNotExpired() && thumbprintIsValid();
}
/**
* Check certificate's timestamp.
* @return True if the certificate isn't expired. It returns False if it is expired.
*/
private boolean certificateHasNotExpired() {
Date currentTime = new java.util.Date();
try {
this.getCertificate().checkValidity(currentTime);
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
return false;
}
return true;
}
/**
* Check whether the certificate's thumbprint matches the given one.
* @return True if the thumbprints match. False otherwise.
*/
private boolean thumbprintIsValid() throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = this.getCertificate().getEncoded();
md.update(der);
byte[] digest = md.digest();
String digestHex = DatatypeConverter.printHexBinary(digest);
return digestHex.toLowerCase().equals(this.getThumbprint().toLowerCase());
}
// Getters and setters.
public void setThumbprint(String thumbprint) {
this.thumbprint = thumbprint;
}
public String getThumbprint() {
return this.thumbprint;
}
public X509Certificate getCertificate() {
return certificate;
}
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
}
}
Python-voorbeeld
In de volgende Flask- en Django Python-codevoorbeelden wordt een decorator geïmplementeerd authorize_certificate
die kan worden gebruikt voor een weergavefunctie om alleen toegang te verlenen tot bellers die een geldig clientcertificaat presenteren. Er wordt een certificaat in PEM-indeling verwacht in de X-ARR-ClientCert
header en het cryptografie-pakket van Python wordt gebruikt om het certificaat te valideren op basis van de vingerafdruk (thumbprint), de algemene naam van het onderwerp, de algemene naam van de uitgever, en de begin- en vervaldatum. Als de validatie mislukt, zorgt de decorator ervoor dat een HTTP-antwoord met statuscode 403 (Verboden) wordt geretourneerd naar de client.
from functools import wraps
from datetime import datetime, timezone
from flask import abort, request
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
def validate_cert(request):
try:
cert_value = request.headers.get('X-ARR-ClientCert')
if cert_value is None:
return False
cert_data = ''.join(['-----BEGIN CERTIFICATE-----\n', cert_value, '\n-----END CERTIFICATE-----\n',])
cert = x509.load_pem_x509_certificate(cert_data.encode('utf-8'))
fingerprint = cert.fingerprint(hashes.SHA1())
if fingerprint != b'12345678901234567890':
return False
subject = cert.subject
subject_cn = subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
if subject_cn != "contoso.com":
return False
issuer = cert.issuer
issuer_cn = issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
if issuer_cn != "contoso.com":
return False
current_time = datetime.now(timezone.utc)
if current_time < cert.not_valid_before_utc:
return False
if current_time > cert.not_valid_after_utc:
return False
return True
except Exception as e:
# Handle any errors encountered during validation.
print(f"Encountered the following error during certificate validation: {e}")
return False
def authorize_certificate(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not validate_cert(request):
abort(403)
return f(*args, **kwargs)
return decorated_function
In het volgende codefragment ziet u hoe u de decorator gebruikt in een Flask-weergavefunctie.
@app.route('/hellocert')
@authorize_certificate
def hellocert():
print('Request for hellocert page received')
return render_template('index.html')