Подписанные запросы (Signed Requests) используются для контроля доступа к ресурсам, которые загружаются картой. В контексте Mapbox GL JS этот механизм позволяет добавлять специальные параметры аутентификации к запросам тайлов, стилей, шрифтов, изображений, GeoJSON-данных и других ресурсов.
Подписывание запросов особенно важно в случаях:
В отличие от стандартного использования публичного access token, подписанные запросы позволяют гибко контролировать доступ к каждому отдельному ресурсу.
При отображении карты библиотека автоматически выполняет множество HTTP-запросов:
Например:
/styles/v1/example/style
/tiles/12/2345/1234.pbf
/fonts/v1/Arial Unicode/0-255.pbf
/sprites/sprite.json
По умолчанию каждый запрос выполняется без дополнительной модификации, кроме передачи access token, если используются сервисы Mapbox.
Для вмешательства в процесс формирования запроса применяется механизм
transformRequest.
Параметр transformRequest позволяет перехватывать каждый
запрос перед его отправкой.
Общий синтаксис:
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
transformRequest: (url, resourceType) => {
return {
url: url
};
}
});
Функция получает:
url — исходный адрес ресурса;resourceType — тип загружаемого ресурса.Возвращаемый объект определяет итоговые параметры запроса.
Аргумент resourceType позволяет различать категории
загружаемых данных.
Наиболее распространённые значения:
| Тип | Описание |
|---|---|
| Style | JSON-стиль карты |
| Source | Источник данных |
| Tile | Векторный или растровый тайл |
| Glyphs | Шрифты |
| SpriteImage | Изображение спрайта |
| SpriteJSON | Описание спрайта |
| Image | Дополнительные изображения |
Пример проверки:
transformRequest: (url, resourceType) => {
if (resourceType === 'Tile') {
console.log(url);
}
return { url };
}
Такой подход позволяет подписывать только необходимые запросы.
Самый простой вариант подписывания — добавление параметров в URL.
Исходный адрес:
https://api.example.com/tiles/10/500/300.pbf
После модификации:
https://api.example.com/tiles/10/500/300.pbf?token=abc123
Реализация:
transformRequest: (url) => {
const signedUrl =
`${url}?token=abc123`;
return {
url: signedUrl
};
}
Однако такой подход подходит только для простых схем авторизации.
Часто сервер ожидает несколько параметров:
Пример:
transformRequest: (url) => {
const expires = Date.now() + 60000;
const signedUrl =
`${url}?client=42&expires=${expires}&signature=XYZ`;
return {
url: signedUrl
};
}
На стороне сервера выполняется проверка подписи и времени действия ссылки.
Для корректной работы с параметрами рекомендуется использовать объект
URL.
transformRequest: (url) => {
const requestUrl = new URL(url);
requestUrl.searchParams.set(
'token',
'abc123'
);
return {
url: requestUrl.toString()
};
}
Преимущества:
Многие API используют HTTP-заголовки вместо параметров URL.
Например:
Authorization: Bearer token
Реализация:
transformRequest: (url) => {
return {
url,
headers: {
Authorization:
'Bearer my-secret-token'
}
};
}
После этого каждый запрос будет содержать указанный заголовок.
Одним из наиболее распространённых способов подписывания запросов является применение JWT.
Пример:
const jwtToken =
localStorage.getItem('jwt');
const map = new mapboxgl.Map({
container: 'map',
style: styleUrl,
transformRequest: (url) => {
return {
url,
headers: {
Authorization:
`Bearer ${jwtToken}`
}
};
}
});
JWT может содержать:
Не рекомендуется отправлять секретные данные на все адреса.
Правильнее ограничивать область действия подписи.
transformRequest: (url) => {
if (
url.startsWith(
'https://api.company.com'
)
) {
return {
url,
headers: {
Authorization:
'Bearer token'
}
};
}
return { url };
}
Такой подход предотвращает утечку токенов сторонним сервисам.
Часто сервер генерирует ссылки с ограниченным сроком действия.
Пример:
https://api.company.com/tile.pbf
?expires=1710000000
&signature=8d2f2c4d
Сервер проверяет:
Даже при перехвате URL злоумышленник сможет использовать его лишь ограниченное время.
Иногда подпись вычисляется непосредственно в браузере.
Пример схемы:
const timestamp =
Date.now().toString();
const signature =
generateSignature(
url,
timestamp
);
Далее:
transformRequest: (url) => {
return {
url:
`${url}?ts=${timestamp}` +
`&sig=${signature}`
};
}
Однако хранение секретного ключа на клиенте считается небезопасным решением.
Наиболее безопасный вариант предполагает генерацию подписей на сервере.
Схема работы:
Browser
|
| Запрос подписи
v
Backend
|
| Генерация подписи
v
Signed URL
|
| Использование в Mapbox GL JS
v
Protected Resource
Преимущества:
Источник данных может быть защищён авторизацией.
Пример:
map.addSource('cities', {
type: 'geojson',
data:
'https://api.company.com/cities'
});
Подписывание:
transformRequest: (url, type) => {
if (type === 'Source') {
return {
url,
headers: {
Authorization:
'Bearer token'
}
};
}
return { url };
}
В результате GeoJSON будет загружаться только после успешной проверки прав доступа.
Чаще всего защита применяется именно к тайлам.
Пример:
transformRequest: (
url,
resourceType
) => {
if (resourceType === 'Tile') {
return {
url,
headers: {
Authorization:
'Bearer token'
}
};
}
return { url };
}
При перемещении карты каждый тайл автоматически получает подпись.
Некоторые системы безопасности используют авторизационные cookie.
Для передачи cookie необходимо включить учётные данные.
transformRequest: (url) => {
return {
url,
credentials: 'include'
};
}
Теперь браузер будет отправлять:
Cookie: SESSION_ID=12345
для соответствующего домена.
Поддерживаются несколько режимов.
Cookie не отправляются.
credentials: 'omit'
Cookie передаются только для того же источника.
credentials: 'same-origin'
Cookie отправляются всегда.
credentials: 'include'
Выбор режима зависит от политики безопасности приложения.
Подписанные запросы часто выполняются между разными доменами.
Сервер должен разрешить:
Access-Control-Allow-Origin
а также:
Access-Control-Allow-Headers
Например:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Headers: Authorization
Без корректной настройки CORS браузер заблокирует запрос ещё до его выполнения.
При использовании временных токенов необходимо отслеживать их актуальность.
Пример:
function getToken() {
if (tokenExpired()) {
refreshToken();
}
return currentToken;
}
Далее:
transformRequest: (url) => {
return {
url,
headers: {
Authorization:
`Bearer ${getToken()}`
}
};
}
Таким образом карта продолжает работать даже после обновления токена.
В крупных приложениях логика подписывания обычно выносится в отдельный модуль.
class AuthManager {
getToken() {
return this.token;
}
getHeaders() {
return {
Authorization:
`Bearer ${this.token}`
};
}
}
Использование:
const auth =
new AuthManager();
transformRequest: (url) => {
return {
url,
headers:
auth.getHeaders()
};
}
Подобная архитектура упрощает сопровождение проекта.
Для анализа сетевой активности используются инструменты разработчика браузера.
Проверяются:
Полезно контролировать наличие:
401 Unauthorized
и
403 Forbidden
Код 401 обычно означает отсутствие корректной
аутентификации, а 403 свидетельствует о недостатке прав
доступа.
Не передавать секретные ключи в клиентском коде.
Даже минифицированный JavaScript может быть изучен и декомпилирован.
Подписывать только необходимые запросы.
Не следует добавлять токены к сторонним ресурсам.
Использовать короткое время жизни подписей.
Временные URL значительно снижают риск компрометации.
Применять HTTPS.
Передача токенов по HTTP создаёт угрозу их перехвата.
Регулярно обновлять токены.
Механизм refresh token позволяет поддерживать непрерывную работу карты без повторной аутентификации пользователя.
Логировать операции проверки доступа.
Серверные журналы позволяют выявлять попытки несанкционированного использования картографических ресурсов.
Комбинировать несколько механизмов защиты.
Наиболее надёжными считаются схемы, в которых одновременно используются:
Такой подход обеспечивает безопасную работу Mapbox GL JS с приватными стилями, тайлами и пользовательскими геоданными в корпоративных и высоконагруженных веб-приложениях.