Ориентация главы

«Добавить аутентификацию» в HTTP — задача выбора из трёх семейств схем.Basic: Authorization: Basic base64(用户:密码) (пользователь:пароль) — открытый текст (приемлемая форма только под TLS), по строке на каждом конце.Bearer: Authorization: Bearer <令牌> (токен) — носитель эпохи OAuth2/JWT; клиент только несёт его.Digest: сервер выдаёт nonce-вызов, клиент отвечает H(用户:域:密码) (пользователь:область:пароль) — протокол вызов-ответ, где пароль не выходит в сеть, со встроенной защитой от повторов (nonce + счётчик nc); сервер ведёт жизненный цикл nonce, таблицу повторов и квитанцию Authentication-Info. xhttp реализует оба конца всех трёх схем как структурированные API: построение на клиенте (xrtHttpBasicWrite/BearerWrite/ответ Digest), чтение на сервере (три состояния xrtHttpServerRequestBasicAuth/BearerAuth/DigestAuth), полная цепочка проверки Digest (четыре состояния verify у xrtHttpDigestVerify + потокобезопасная таблица повторов). Отсюда собирается middleware авторизации главы 105.

106.1引入

Три формы развёртывания, каждая на своём месте. Инструменты интрасети: Basic + TLS достаточно — по строке кода на каждом конце; пароль открытый текст, но туннель шифрованный. API-платформы: Bearer — выдача/обновление токенов дело платформы, сервер только проверяет токен; «аутентификация» клиента — просто принести его. Недоверенная среда или протокольное соответствие (защита пароля в семантике HTTP): Digest — дайджесты паролей на сервере, вызов-ответ защищает от повторов; цена — сервер поддерживает состояние nonce и повторов.Цена неправильного выбора: Basic поверх открытого текста HTTP равносилен трансляции пароля;.stateless Bearer на платформе, где нужно отзывать токены, равносилен невозможности отзыва; использовать Digest как «более безопасный Basic», не управляя истечением nonce, — окно повторов открыто как прежде.

Единая форма auth-API заслуживает внимания: всё идёт через три состояния XHTTP_NEXT_END/ITEM/ERROR (отсутствует/действителен/ошибка) — тот же контракт, что у структурированного чтения полей (главы 92/104); построение вызова «дописывает» (AddChallenge — сосуществование многих схем), квитанция «устанавливает» (SetDigestInfo — единственное поле) — многозначная/единственная семантика RFC ложится на форму API.

106.2概念

Basic: по строке на каждом конце и базовая линия открытого текста

Клиент xrtHttpBasicWrite(用户, 密码, 输出, 容量, &长度) (пользователь, пароль, вывод, ёмкость, &длина) производит значение поля Basic <base64>; сервер xrtHttpServerRequestBasicAuth(请求, 解码缓冲, 容量, &长度, &描述符) (запрос, буфер декодирования, ёмкость, &длина, &дескриптор) — результаты декодирования Basic и Digest заимствуют буфер вызывающего (короткий буфер публикует только точную нужную длину; ошибка очищает дескриптор, оставляя длину и тело вызывающего нетронутыми — половину открытого текста вперёд не фиксируем). Дисциплина базовой линии: Basic только под TLS; Basic поверх открытого текста http — авария конфигурации.

Bearer: ношение токенов и вызовы

Клиент xrtHttpBearerWrite производит Bearer <token>; сервер xrtHttpServerRequestBearerAuth берёт представление токена (заимствует снимок запроса — сам токен принадлежит вашему слою проверки: локальная проверка подписи, запрос к базе, вызов сервиса авторизации).Граница безопасности Bearer — в управлении токенами: выдача, истечение, отзыв, области минимальных прав — протокольный слой лишь носитель; вход построения вызова 401 (WWW-Authenticate: Bearer error="invalid_token") ведает синтаксисом, политика — приложению.

Digest: вызов, ответ и четыре состояния проверки

1Вызовсервер 401 + WWW-Authenticate: Digest realm/nonce/qop/algorithm/opaque (AddChallenge)
2Ответклиент кэширует H(A1)=H(пользователь:realm:пароль); считает request-digest по nonce+nc
3Проверкасервер ищет H(A1) (по имени пользователя/userhash) -> заполняет дескриптор проверки (метод/target/вызов)
4Квитанцияпосле VALID DigestRspAuth генерирует rspauth -> SetDigestInfo ставит Authentication-Info
5Повторытолько VALID фиксирует nc; ReplayCheck - потокобезопасная таблица повторов (распределённо ReplayKey + общее хранилище)

Точная семантика четырёх состояний: XHTTP_DIGEST_VERIFY_VALID (подпись и доказательство верны), _STALE (подпись и доказательство верны, но nonce истёк — клиенту следует повторить со свежим nonce; это не провал), _INVALID (доказательство неверно — отвергнуть), _ERROR (ошибка ввода/структуры).Только VALID фиксирует монотонный nc — ранняя фиксация позволила бы ошибочному доказательству захватить счёт легитимного клиента (таблица повторов отравлена). Управление nonce: встроено в вход по умолчанию; собственные nonce или вращающееся кольцо ключей — комбинация двух уровней xrtHttpDigestProofVerify+NonceVerify. Квитанция Authentication-Info (взаимная аутентификация rspauth + вращение nextnonce) генерируется после успешной проверки из того же контекста доказательства — полный замкнутый цикл вызова и ответа.

Формы развёртывания защиты от повторов

Один процесс: xrtHttpDigestReplayCheck — потокобезопасная таблица повторов с жёсткой ёмкостью; много процессов/распределённо: xrtHttpDigestReplayKey генерирует канонический ключ, общее хранилище делает атомарное обновление максимума с истечением (nc — монотонный счётчик — max-обновление с семантикой CAS). Окно повторов = срок nonce; срок — параметр баланса безопасности и опыта.

Разбор вызовов на клиенте

xrtHttpChallengeNext итерирует многосхемные вызовы в ответе 401 (Digest realm=..., Basic ... смешанные объявления) — клиент выбирает схему по возможностям: кэш пароля — Digest, токен — Bearer, откат — Basic. Слой политики автоматического повтора (глава 100), получив 401, собирается отсюда.

106.3示例

Первая полная программа: по-схемный разбор вызова сервера

Программа ниже из examples/http/auth — структурированное чтение многосхемного вызова:

extlibs/xhttp/examples/http/auth/main.c源码 ↗
#include <stdio.h>

#include <xhttp.h>



/* 逐项读取服务端返回的认证 challenge。 */
int main(void)
{
	xstrview Value = XRT_STR_LITERAL(
		"Digest realm=\"api\", Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
	);
	xhttpauth Challenge;
	xhttpnext Next;
	size_t iOffset = 0;

	while ( (Next = xrtHttpChallengeNext(
		Value, &iOffset, &Challenge
	)) == XHTTP_NEXT_ITEM ) {
		printf("%.*s\n",
			(int)Challenge.Scheme.Size,
			Challenge.Scheme.Data);
	}
	return (Next == XHTTP_NEXT_END) ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/auth/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapiDigestBasic

Что только что произошло. (1) Ввод — смешанный вызов Digest realm="api", Basic QWxhZGRpbjpvcGVuIHNlc2VtZQ== — два объявления схем в одном значении WWW-Authenticate. (2) xrtHttpChallengeNext итерирует по смещению, выдавая каждый xhttpauth (представление Scheme + параметры) — по позиции на Digest и Basic; та же форма, что итерация семейства полей главы 92 (домен запятых, смещение в руках). (3) Отправная точка клиентской политики: сканировать по порядку, сопоставлять по возможностям — «что объявил сервер» — структурированный факт, а не угадывание по строкам. Параметры Digest (realm/nonce/qop) продолжают разбираться параметрическим слоем главы 92.

Вторая полная программа: кратчайший путь обоих концов Basic

Вторая программа из examples/http/auth_basic — построение на клиенте:

extlibs/xhttp/examples/http/auth_basic/main.c源码 ↗
#include <stdio.h>

#include <xhttp.h>



/* 构建 Basic Authorization 字段值。 */
int main(void)
{
	char Value[128];
	size_t iSize;

	if ( !xrtHttpBasicWrite(
		XRT_STR_LITERAL("Aladdin"),
		XRT_STR_LITERAL("open sesame"),
		Value,
		sizeof(Value),
		&iSize
	) ) {
		return 1;
	}
	printf("%.*s\n", (int)iSize, Value);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/auth_basic/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapiBasic QWxhZGRpbjpvcGVuIHNlc2VtZQ==

Что только что произошло. (1) xrtHttpBasicWrite("Aladdin", "open sesame") производит полное значение поля одним вызовом — Base64 (глава 28) встроен, атомарность ёмкости как по всей библиотеке. (2) Вывод — ровно каноническое примерное значение RFC 7617 — стандартные векторы снова якорят корректность реализации. (3) Серверная противоположность xrtHttpServerRequestBasicAuth декодирует в буфер вызывающего — четыре вызова на оба конца, и это весь Basic. Семья сопроводительных образцов: auth_bearer (построение токена), auth_digest (полный вызов-ответ), auth_digest_client/auth_digest_session (сессии и переиспользование nonce), auth_digest_nonce/_replay (nonce и повторы), digest_sha2 (семейство алгоритмов SHA-2) — у каждого отрезка Digest свой образец.

106.4契约

  • Три состояния едины: семейство чтения END (отсутствует)/ITEM (действителен)/ERROR (дублирующееся поле, нелегальный синтаксис, несовпадение схемы, провал декодирования); дублирующийся Authorization — протокольная ошибка Header.
  • Граница заимствования: общие результаты и Bearer заимствуют снимок запроса; результаты декодирования Basic и Digest заимствуют буфер вызывающего; короткий буфер публикует точную длину, ошибка не трогает состояние вызывающего.
  • Базовая линия Basic: открытый текст (base64 не шифрование); только под TLS; по одному вызову API на каждый конец.
  • Граница Bearer: проверка/отзыв/права токена — прикладному слою; построение синтаксиса вызова — слою аутентификации.
  • Четыре состояния Digest: VALID/STALE (подпись верна, nonce истёк — повторяемо)/INVALID (доказательство неверно)/ERROR; только VALID фиксирует nc.
  • Управление nonce: по умолчанию встроено; собственное — комбинация ProofVerify+NonceVerify; срок = окно повторов.
  • Таблица повторов: один процесс ReplayCheck (потокобезопасная, жёсткий потолок); распределённо ReplayKey + общее хранилище с атомарным max.
  • Вызов/квитанция: Add дописывает многие схемы; Set ставит единственный Authentication-Info (rspauth+nextnonce); статус 401/407 автоматически не меняется — политика приложению.
  • Обрезка: HTTP_AUTH/HTTP_DIGEST независимы; семейство алгоритмов SHA-2 опционально.

Типичные ошибки

Ловушка 1: Basic поверх открытого текста HTTP:Ловушка 1: Basic поверх открытого текста HTTP

Симптом: захват пакетов видит Authorization: Basic <base64(密码)> (пароль) — пароль фактически транслируется.

Причина: base64 — кодирование, не шифрование. Вся безопасность Basic — от транспортного слоя; под TLS он уровня «защита от подглядывания через плечо».

错误写法
/* http:// интрасетевой адрес + Basic: в захвате/журнале прокси интрасети сплошные пароли открытым текстом */
xrtHttpRequestSetBasicAuth(pReq, User, Pass);
send_http(url);   /* без TLS */
正确写法
/* Basic идёт только с https */
if ( !url_is_https(pUrl) ) {
	return error("basic auth requires TLS");
}
xrtHttpRequestSetBasicAuth(pReq, User, Pass);
Ловушка 2: проверка Digest не различает STALE и валит всё как провал:Ловушка 2: проверка Digest не различает STALE и валит всё как провал

Симптом: легитимный клиент с истёкшим nonce получает 401 и вылетает из логина — опыт ломается; или наоборот, INVALID принят за STALE с разрешением повтора — брутфорс-доказательства крутятся бесконечно.

Причина: четыре состояния обязаны обрабатываться по веткам: STALE — «начните заново со свежим nonce» (клиент уже имеет H(A1) и повторяет бесшовно), INVALID — «доказательство просто неверно» (отвергнуть и посчитать).

错误写法
switch ( xrtHttpDigestVerify(&V) ) {
default:
	reject_and_logout();   /* STALE тоже принят за провал */
}
正确写法
xhttpdigestverifycheck R = xrtHttpDigestVerify(&V, Key, Ctx, Now, Life, Skew, NULL);
if ( R == XHTTP_DIGEST_VERIFY_VALID ) { accept(); }
else if ( R == XHTTP_DIGEST_VERIFY_STALE ) {
	challenge_with_fresh_nonce();   /* клиент повторит со свежим nonce */
} else {
	reject_and_count();             /* INVALID/ERROR: отвергнуть + посчитать */
}
Ловушка 3: фиксация счётчика nc до проверки:Ловушка 3: фиксация счётчика nc до проверки

Симптом: счётчик таблицы повторов захвачен ошибочными доказательствами — следующий запрос легитимного клиента признаётся повтором и отвергается.

Причина: nc — монотонный счёт «запросов, уже израсходованных под этим nonce»; запрос INVALID не должен занимать номер — контракт ясно говорит «фиксирует только VALID».

错误写法
verify_partial(&V);                 /* проверена только подпись */
replay_commit(nonce, nc);           /* занять номер сперва - ошибся или нет */
if ( full_verify(&V) ) { accept(); }
正确写法
if ( xrtHttpDigestVerify(&V, Key, Ctx, Now, Life, Skew, NULL) ==
		XHTTP_DIGEST_VERIFY_VALID ) {
	replay_commit(nonce, nc);       /* номер - только при полностью верном доказательстве */
	accept();
}

Практические задания

Базовое: матрица вызовов трёх схем

Постройте вызов с Digest+Basic+Bearer; клиент разбирает и выбирает схему по конфигурации (есть пароль/есть токен/ничего), печатая выбор. Критерии приёмки: все три конфигурации выбирают верную схему; параметры вызова (realm/nonce) читаются как структура.

Продвинутое: замкнутый цикл вызов-ответ Digest

Сервер (сервер + middleware главы 104) выдаёт вызов Digest; клиент разбирает nonce, считает ответ, отправляет; серверный DigestVerify идёт по четырём веткам. Повторите запрос, проверьте рост nc. Критерии приёмки: первый запрос VALID; повтор того же nonce отвергнут таблицей повторов; истёкший nonce проходит через STALE, и повтор успешен.

Вызов: шлюз Bearer с отзывом токенов

Middleware шлюза Bearer: поиск токена в локальном кэше (Map + TTL главы 18), при промахе — в сервис авторизации (симуляция); список отзыва поддерживается (отзыв инвалидирует кэш). Критерии приёмки: действительный токен проходит, недействительный 401, отозванный — 401 немедленно; путь попадания в кэш без вызовов бэкенда (проверка подсчётом); токены никогда не попадают в журнал (дисциплина главы 77).

Итоги

ТемаКратко
Три схемыBasic (открытый текст + базовая линия TLS) / Bearer (носитель токена) / Digest (вызов-ответ)
Чтение трёх состоянийEND отсутствует / ITEM действителен / ERROR дубль·нелегальность·несовпадение·провал декодирования
Граница заимствованияобщие и Bearer заимствуют запрос; декодирование Basic/Digest заимствует буфер вызывающего (короткий буфер публикует точную длину)
Построение вызоваAddChallenge дописывает многие схемы; входы Basic/Bearer/Digest каждый полностью валидируются
Четыре состояния DigestVALID / STALE (nonce истёк, повторяемо) / INVALID (доказательство неверно) / ERROR
Дисциплина ncтолько VALID фиксирует монотонный счёт - ранняя фиксация отдаёт номера ошибочным доказательствам
Nonceпо умолчанию встроен; собственный ProofVerify+NonceVerify; срок = окно повторов
Таблица повтороводин процесс ReplayCheck; распределённо ReplayKey + атомарный max общего хранилища
Квитанцияпосле VALID RspAuth генерирует, SetDigestInfo ставит единственный Authentication-Info
КлиентChallengeNext итерирует многие схемы и выбирает по возможностям; H(A1) кэшируется локально

📖 Справочники API в этой главе:xhttp-http_auth (64 записей)xhttp-http_server (75 записей)xhttp-http_digest (13 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки