Postman: Basic авторизация через скрипт
Первое изображение для статьи =) привлечение аудитории
Всем привет, меня зовут Алексей Нихаенко и я дата инженер. Это мой первый пост на Хабре и я хочу поведать вам свое более близкое знакомство с инструментом Postman.
О чем пойдет речь?
Что такое базовая авторизация и способы использования внутри Postman
Задача, которую я преследовал и зачем понадобилась автоматизация (Pre-Request Script)
Простые примеры скрипта с Basic Authorization в Pre-Request Script
Строим дерево вариантов поведения скрипта
Итоговый скрипт, заключение
1. Что такое базовая авторизация и способы использования внутри Postman
Базовая авторизация (Basic Auth) — авторизация, использованная с помощью имени пользователя (username
) и пароля (password
). Отправляется на сервер заголовком (header
) шаблонно вот так:
'Authorization': f'Basic {base64(username:password)}'
Где:
Authorization — наименование заголовка (ключ)
f«Basic {base64(username: password)}» — закодированное в base64 пара имя_пользователя: пароль
Например, если имя пользователя у нас UserNameRandom, а пароль AnyPassword, то вот так по итогу будет выглядеть заголовок:
'Authorization': 'Basic VXNlck5hbWVSYW5kb206QW55UGFzc3dvcmQ='
Закодировать (Encode) в Base64 можете сами онлайн ССЫЛКА
Декодировать (Decode) из Base64 в строку можете сами онлайн ССЫЛКА
Авторизоваться базово внутри Postman можно двумя способами:
Базовая авторизация через встроенный механизм Postman
Задача, зачем нужна автоматизация
Есть Jira. Некоторые запросы должны получать задачи (GET-запросы), некоторые — переводить статус задачи в другой статус (в моем случае из «Новый» в «Отменен»). Я со своей автоматизацией хотел покрыть несколько задач:
Хочу попробовать отправить запрос с правильными логинами-паролями
Отправить запрос с неправильными логином-паролем
Отправить запрос без заголовка Authorization
Максимально защитить логины-пароли, чтобы, делясь коллекцией, я «не палил» их
Сохранить результаты запросов в Example максимально секурно (см. пункт выше) и чтобы было понятно — использовался ли заголовок авторизации и какой
В связи с вышеизложенным мне хотелось как можно меньше ручных манипуляций. В голову пришла фича Postman, с помощью которой можно до отправки запроса устанавливать заголовки (и вообще много чего еще) в автоматическом режиме с помощью скриптинга на JavaScript — Pre-Request Script (простите за тавтологию, постараюсь больше не использовать)
Простые примеры скрипта с Basic Authorization
Прежде чем перейти к скриптингу, нужно подумать -, а где мы будем хранить логины-пароли? На ум приходят три вещи:
Переменные внутри коллекции
Переменные внутри скрипта
Переменные внутри окружения
Переменные внутри окружения безопасны за счет не только того, что они скрыты визуально (если вдруг попадут в скрин), но и при «поделиться коллекцией» вы не передаете Credentials (учетные данные, логин-пароль)
Следующий вопрос -, а куда помещаем скрипт? Я отвечу сразу — на уровне коллекции, чтобы при создании новых запросов заголовок автоматически применялся
Еще важный момент — внутрь коллекции я создал переменную base_url со значением «https://jira.com». В запросах могут фигурировать {{base_url}} — это Postman берет переменную из коллекции и применяет в качестве URL запроса
Итак, приступаем к кодингу. Начнем с самого простого, пойдем потом на усложнение.
Представим, что у нас переменные внутри скрипта, никаких заголовков не устанавливалось на уровне запросов, тогда нам нужно добавить заголовок. Обратите, пожалуйста, внимание на различие add и upsert (в будущем, на усложнении понадобится)
// Зашитые внутрь скрипта креденшелы
const login = "UserNameRandom";
const password = "AnyPassword";
// Кодирование в base64
const encodedCredentials = Buffer.from(`${login}:${password}`).toString('base64');
// Установка заголовка Authorization
// add используется для добавления заголовка
// upsert используется для обновления (хотя должен для вставки и обновления)
pm.request.headers.add({
key: "Authorization",
value: `Basic ${encodedCredentials}`
});
// Выводим опционально значение в консоль Postman
console.log("Заголовок Authorization добавлен:", `Basic ${encodedCredentials}`);
Результат мы можем увидеть на следующем экране
Результат выполнения скрипта по добавлению заголовка Authorization
Ниже — скрипты по взятию переменных из коллекции и из окружения
var login = "UserNameRandom";
var password = "AnyPassword";
console.log(`Зашитые внутри скрипта логин: ${login} и пароль ${password}`);
login = pm.collectionVariables.get("login");
password = pm.collectionVariables.get("password");
console.log(`Переменные коллекции, логин: ${login} и пароль ${password}`);
login = pm.environment.get("login")
password = pm.environment.get("password");
console.log(`Переменные окружения, логин: ${login} и пароль ${password}`);
Строим дерево вариантов поведения скрипта
Теперь, когда какой-никакой, а скрипт есть, нужно его улучшить, учитывая несколько вопросов \ нюансов:
Вопрос | Варианты поведения |
Переменные login или password отсутствуют | Запрос отправляется без изменений в заголовках |
Прерываем работу и скрипта и запроса | |
Заголовок Authorization отсутствует | Добавляем |
Не добавляем, запрос отправляется | |
Не добавляем, запрос НЕ отправляется | |
Заголовок Authorization присутствует | Изменяем его в любом случае |
Изменяем его по условию | |
Не изменяем никогда, запрос отправляется | |
Не изменяем никогда, запрос НЕ отправляется | |
Заголовок Authorization присутствует, но он отключен | Нужно учитывать только включенные |
Заголовков Authorization несколько и все они активные | Выдавать ошибку, что заголовков несколько |
Брать какой-то 1 заголовок по условию и см. выше — изменять ли его? | |
Заголовка Authorization нет, однако меня в систему пускает. Почему? | Авторизация и не нужна |
Сохранились куки, их нужно чистить перед запросом |
Тут, наверное, стоит пояснить -, а что значит включенные (активные) \ отключенные заголовки? Объясняю — бывает так, что в одном запросе ты накидаешь несколько заголовков с одинаковым ключом, но разным значением, чтоб в какой-то момент подключать нужный. Вот как это выглядит визуально:
Признак активности заголовка в Postman
Прежде чем перейти к тому, как ответил я, давайте разбираться со всем в коде:
// Получение переменных из окружения
const login = pm.environment.get("login");
const password = pm.environment.get("password");
if (!login || !password) {
// Определяем переменную для вывода в консоль
var reason_error = "Не найдены переменные login или password"
// Выводим лог в консоль. Не прерывает работу ни скрипта, ни запроса
console.error(reason_error);
// Прерываем работу, выбрасывая ошибку
throw new Error(reason_error)
}
// Наименование (ключ) заголовка
var header_name = "Authorization"
// Смотрим на отключенные заголовки header_name
const disabledHeaders = pm.request.headers.filter(header => header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Считаем количество отключённых заголовков header_name
const disabledCount = disabledHeaders.length;
if (disabledCount >= 1) {
console.log(`Количество отключенных заголовков ${header_name} = ${disabledCount}`)
}
else {
console.log("Нет отключенных заголовков")
}
Хм, а как посмотреть включенные заголовки Authorization? Да то же самое, только вместо header.disabled пишем ! header.disabled
А как посмотреть вообще все заголовки?
const authHeader = pm.request.headers.get("Authorization");
Шаг 1. Проваливаемся в Cookies
Шаг 2. Проваливаемся в Domains Allowlist
Шаг 3. Добавляем сайт в Domains Allowlist
Отлично, основные моменты кода вспомнили, теперь давайте определяться с последовательностью действий с учетом вопросов \ нюансов.
Перед запросом очищаем все куки
Берем активные заголовки Authorization без учета регистра
Если их больше 1 — выдаем ошибку, прерываем работу скрипта и запроса
Если их 0 — значит так и задумано пользователем, просто отправляем запрос дальше
Если их 1 — обновляю только в случае, если в значении встречаются <*calc*>, где * — любой символ и не чувствителен к регистру
— заменится значение, т.к. встречаются все символы — тоже заменится. Напомню, что ищем не чувствительное к регистру — Не заменится Symbol
— не заменится, т.к. символ »<" не идет первым symbol — не заменится, т.к. символ »>» не идет последним
Идем получать переменные окружения. Если чего-то нет — ошибка, прерываем все.
Таким образом мы:
прерываем возможность несколько активных Authorization засунуть в запрос
даем возможность без Authorization отправить запрос
даем возможность со своим Authorization отправить запрос (он же бывает не только Basic, правильно?)
когда нам действительно нужны логин и пароль — проверяем их наличие и выдаем ошибку, если чего-то нет
Итоговый скрипт, заключение
Что-то мне кажется, что за меня все скажет итоговый скрипт, который я использую
// ====================================================
// ====================================================
// ТУТ НИЧЕГО НЕ ТРОГАТЬ!!!
// ====================================================
// ====================================================
// Функция для установки заголовка Authorization
function setAuthorizationHeader() {
// Получение переменных из коллекции
const login = pm.environment.get("login");
const password = pm.environment.get("password");
if (!login || !password) {
var reason_error = "Не найдены переменные login или password"
// Логируем с прерыванием
throw new Error(reason_error)
// Логируем с продолжением работы скрипта
// console.error(reason_error);
return;
}
// Кодирование в base64 (используем Buffer для совместимости с Node.js)
const encodedCredentials = Buffer.from(`${login}:${password}`).toString('base64');
// Установка заголовка Authorization
pm.request.headers.upsert({
key: "Authorization",
value: `Basic ${encodedCredentials}`
});
// console.log("Заголовок Authorization добавлен:", `Basic ${encodedCredentials}`);
}
// Очищаем cookies перед выполнением всех шагов
const var_cookies = pm.cookies.jar();
var_cookies.clear(pm.request.url, function (error) {
// error -
});
// Наименование (ключ) заголовка
var header_name = "Authorization"
// Фильтрация отключенных заголовков header_name
const disabledHeaders = pm.request.headers.filter(header => header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Считаем количество отключенных header_name
const disabledCount = disabledHeaders.length;
// Фильтрация включенных заголовков header_name
const enabledHeaders = pm.request.headers.filter(header => !header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Подсчёт количества включенных header_name
const enabledCount = enabledHeaders.length;
console.log("Количество отключённых заголовков Authorization:", disabledCount);
console.log(`Количество включенных заголовков Authorization: ${enabledCount}`);
// ====================================================
// ====================================================
// РЕДАКТИРОВАТЬ ТОЛЬКО БЛОК НИЖЕ
// ====================================================
// ====================================================
// Если количество отключенных заголовком Authorization НЕ РАВНО включенным
//if (disabledCount !== enabledCount) {
if (enabledCount > 1) {
throw new Error(`Заголовков ${header_name} больше 1. Заголовок должен быть таким 1 в запросе`)
} else if (enabledCount === 0) {
console.log("Запрос будет отправлен без заголовка Authorization")
} else {
// Создаем регулярное выражение для поиска "calc" без учёта регистра
const regex = /<.*calc.*>/i;
// Проверяем, есть ли вхождение в переменной первого элемента (и единственного) enabledHeaders
if (regex.test(enabledHeaders[0].value)) {
// console.log('Вхождение "<*calc*>" найдено в значении заголовка.');
console.log(`Заголовок ${header_name} будет изменен согласно скрипту`);
// Установка заголовка
setAuthorizationHeader();
} else {
console.log("Активный заголовок Authorization:",enabledHeaders[0].value);
}
}
И вот несколько скриншотов работы Postman:
Захардкоженный заголовок не изменяется
Запрос не отправился, т.к. 2 активных заголовка Authorization
Отправка запроса без Authorization
Результат работы скрипта. Заголовок нужный найден и заменен
И, конечно, я добился того, ради чего все это затевалось, а именно я просто сохранил результат отправки запроса без боязни случайно «спалить» свой логин и пароль
Результат порадовал =)
Прошу вас, критикуйте, предлагайте, очень буду рад слышать мнение всех вас, золотых моих, умных, чтоб стать и самому лучше и сообществу помочь :-)