Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Вы можете ограничить доступ к приложению Службы приложений Azure, включив различные типы проверки подлинности для приложения. Одним из способов настройки проверки подлинности является запрос сертификата клиента при отправке запроса клиента с помощью протокола TLS/SSL и проверки сертификата. Этот механизм называется взаимной проверкой подлинности или проверкой подлинности сертификата клиента. В этой статье показано, как настроить приложение для использования проверки подлинности сертификата клиента.
Примечание.
Проверку сертификата клиента выполняет код вашего приложения. Служба приложений ничего не делает с этим сертификатом клиента, кроме пересылки его в приложение.
Если вы обращаетесь к сайту по протоколу HTTP и не HTTPS, вы не получаете никаких сертификатов клиента. Если приложению требуются сертификаты клиента, то не следует разрешать запросы к приложению по протоколу HTTP.
Подготовка веб-приложения
Чтобы создать пользовательские привязки TLS/SSL или включить сертификаты клиента для приложения службы приложений, план службы приложений должен находиться на уровнях "Базовый", "Стандартный", "Премиум" или "Изолированный".
Чтобы убедиться, что веб-приложение находится в поддерживаемой ценовой категории:
Перейдите к своему веб-приложению
В поле поиска на портале Azure введите службы приложений и выберите его в результатах поиска.
На панели "Службы приложений" выберите имя веб-приложения.
Теперь вы находитесь на странице управления веб-приложением.
Проверьте ценовую категорию
В меню слева для вашего веб-приложения в разделе «Параметры» выберите «Масштабирование (повышение мощности)» (план службы приложений).
Убедитесь, что веб-приложение не в уровне F1 или D1. Эти уровни не поддерживают пользовательский протокол TLS/SSL.
Если вам нужно расширить масштаб, выполните действия в следующем разделе. В противном случае закройте область масштабирования и пропустите следующий раздел.
Изменение уровня плана службы приложений
Выберите любой небесплатный уровень, например B1, B2, B3 или любой другой уровень в категории Производственный.
После завершения нажмите кнопку "Выбрать".
Когда появится следующее сообщение, операция масштабирования будет завершена.
Включение сертификатов клиента
При включении сертификатов клиента для приложения следует выбрать выбранный режим сертификата клиента. Каждый режим определяет, как приложение обрабатывает входящие сертификаты клиента.
режим сертификата клиента. | Описание |
---|---|
Обязательное поле | Для всех запросов требуется сертификат клиента. |
Необязательно | Запросы могут использовать сертификат клиента и клиенты по умолчанию запрашивают сертификат. Например, клиенты браузера показывают запрос на выбор сертификата для проверки подлинности. |
Необязательный интерактивный пользователь | Запросы могут использовать сертификат клиента и клиенты по умолчанию не запрашивают сертификат. Например, клиенты браузера не отображают запрос на выбор сертификата для проверки подлинности. |
Чтобы использовать портал Azure для настройки приложения, чтобы требовать сертификаты клиента:
- Перейдите на страницу управления приложениями.
- В меню слева выберите"Общие параметры конфигурации>".
- В режиме сертификата клиента выберите нужный вариант.
- Выберите Сохранить.
Исключить пути из требования проверки подлинности
При включении взаимной проверки подлинности для приложения все пути в корневом каталоге приложения требуют сертификата клиента для доступа. Чтобы удалить это требование для отдельных путей, определите исключаемые пути как часть конфигурации приложения.
Примечание.
Использование любого пути исключения сертификата клиента запускает повторное согласование TLS для входящих запросов к приложению.
В меню слева на странице управления приложениями выберите"Общие параметры конфигурации>".
Рядом с путями исключения сертификатов выберите значок редактирования.
Выберите новый путь, укажите путь или список путей, разделенных
,
или;
нажмите кнопку "ОК".Выберите Сохранить.
На следующем снимке экрана любой путь для приложения, начинающегося с /public
, не запрашивает сертификат клиента. Сопоставление путей не является конкретным случаем.
Повторное согласование сертификата клиента и TLS
Для некоторых параметров сертификата клиента Служба приложений требуется повторное согласование TLS для чтения запроса, прежде чем знать, следует ли запрашивать сертификат клиента. Любой из следующих параметров активирует повторное согласование TLS:
- Использование режима клиентского сертификата "Интерактивный пользователь (необязательный)".
- Использование пути игнорирования клиентского сертификата.
Примечание.
TLS 1.3 и HTTP 2.0 не поддерживают повторное согласование TLS. Эти протоколы не работают, если приложение настроено с параметрами сертификата клиента, используюющими повторное согласование TLS.
Чтобы отключить повторное согласование TLS и настроить приложение для согласования клиентских сертификатов во время рукопожатия TLS, необходимо настроить приложение со всеми этими параметрами:
- Задайте для режима сертификата клиента значение "Обязательный" или "Необязательный".
- Удалите все пути исключения сертификатов клиента.
Отправка больших файлов с помощью повторного согласования TLS
Конфигурации сертификатов клиента, использующие повторное согласование TLS, не могут поддерживать входящие запросы с большими файлами размером более 100 КБ из-за ограничений размера буфера. В этом сценарии все запросы POST или PUT более 100 КБ завершались ошибкой 403. Это ограничение не настраивается и не может быть увеличено.
Чтобы устранить ограничение в 100 КБ, рассмотрите следующие альтернативные решения:
- Отключите повторное согласование TLS. Обновите конфигурации сертификатов клиента приложения со всеми этими параметрами:
- Задайте для режима сертификата клиента значение "Обязательный" или "Необязательный".
- Удалите все пути исключения сертификатов клиента.
- Отправьте запрос HEAD перед запросом PUT/POST. Запрос HEAD обрабатывает сертификат клиента.
- Добавьте заголовок
Expect: 100-Continue
в запрос. Это приводит к тому, что клиент ждёт, пока сервер не ответит с помощью100 Continue
, прежде чем отправить тело запроса, что позволяет обойти буферы.
Доступ к сертификату клиента
В службе приложений завершение запроса TLS происходит в интерфейсном подсистеме балансировки нагрузки. Когда Служба приложений пересылает запрос в код приложения с X-ARR-ClientCert
сертификатами клиента, он вводит заголовок запроса с сертификатом клиента. Служба приложений ничего не делает с этим сертификатом клиента, кроме пересылки его в приложение. Проверку сертификата клиента выполняет код вашего приложения.
Для ASP.NET сертификат клиента доступен через HttpRequest.ClientCertificate
свойство.
Для других стеков приложений (Node.js, PHP) сертификат клиента доступен в приложении с помощью значения в кодировке Base64 в заголовке X-ARR-ClientCert
запроса.
пример ASP.NET Core
В ASP.NET Core для анализа перенаправленных сертификатов предоставляется ПО промежуточного слоя. Для использования перенаправленных заголовков протоколов предоставляется отдельное промежуточное ПО. Для принятия перенаправленных сертификатов должны присутствовать оба необходимых элемента. Вы можете разместить кастомную логику проверки сертификата в параметрах 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;
// Only loopback proxies are allowed by default. Clear that restriction to enable this explicit configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
// Configure the application to client certificate forwarded the front-end load balancer
services.AddCertificateForwarding(options => { options.CertificateHeader = "X-ARR-ClientCert"; });
// Add certificate authentication so 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?}");
});
}
}
Пример веб-форм ASP.NET
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. Depending on your application logic and security requirements,
// you should modify this method
//
private bool IsValidClientCertificate()
{
// In this example, we accept the certificate as a valid certificate only if all the conditions below 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 this certificate is chained to a Trusted Root Authority (or revoked) on the server
// and it allows for 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;
}
}
}
Пример на Node.js
Следующий Node.js пример кода получает X-ARR-ClientCert
заголовок и использует node-forge для преобразования строки в кодировке Base64 Privacy Enhanced Mail (PEM) в объект сертификата и проверяет его:
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.CERT
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
Приведенный ниже класс Java кодирует сертификат из X-ARR-ClientCert
в экземпляр X509Certificate
.
certificateIsValid()
Проверяет, совпадает ли отпечаток сертификата, заданный в конструкторе, и что срок действия сертификата не истек.
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 cannot 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 hasn't expired.
* @return True if the certificate's thumbprint matches and hasn't expired. False otherwise.
*/
public boolean certificateIsValid() throws NoSuchAlgorithmException, CertificateEncodingException {
return certificateHasNotExpired() && thumbprintIsValid();
}
/**
* Check certificate's timestamp.
* @return Returns true if the certificate hasn't expired. Returns false if it has expired.
*/
private boolean certificateHasNotExpired() {
Date currentTime = new java.util.Date();
try {
this.getCertificate().checkValidity(currentTime);
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
return false;
}
return true;
}
/**
* Check the certificate's thumbprint matches the given one.
* @return Returns 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
Следующие примеры кода Flask и Django Python реализуют декоратор с именем authorize_certificate
, который можно использовать в функции представления, чтобы разрешить доступ только вызывающим, которые представляют действительный сертификат клиента. Он ожидает сертификат, отформатированный в PEM, в заголовке X-ARR-ClientCert
и использует пакет Python для проверки сертификата на основе его отпечатка, общего имени субъекта, общего имени издателя и дат начала и окончания срока действия. Если проверка завершается ошибкой, декоратор гарантирует, что http-ответ с кодом состояния 403 (запрещено) возвращается клиенту.
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
В следующем фрагменте кода показано, как использовать декоратор в функции представления Flask.
@app.route('/hellocert')
@authorize_certificate
def hellocert():
print('Request for hellocert page received')
return render_template('index.html')