Всем привет, меня зовут Алексей Нихаенко и я дата инженер. Это мой первый пост на Хабре и я хочу поведать вам свое более близкое знакомство с инструментом 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 можно двумя способами:
Через внутренний функционал по авторизации (тогда заголовок он сам подставляет закодированный)
-
Либо подставляя закодированный в Base64 значение заголовка Authorization
Задача, зачем нужна автоматизация
Есть Jira. Некоторые запросы должны получать задачи (GET-запросы), некоторые - переводить статус задачи в другой статус (в моем случае из "Новый" в "Отменен"). Я со своей автоматизацией хотел покрыть несколько задач:
Хочу попробовать отправить запрос с правильными логинами-паролями
Отправить запрос с неправильными логином-паролем
Отправить запрос без заголовка Authorization
Максимально защитить логины-пароли, чтобы, делясь коллекцией, я "не палил" их
Сохранить результаты запросов в Example максимально секурно (см. пункт выше) и чтобы было понятно - использовался ли заголовок авторизации и какой
В связи с вышеизложенным мне хотелось как можно меньше ручных манипуляций. В голову пришла фича Postman, с помощью которой можно до отправки запроса устанавливать заголовки (и вообще много чего еще) в автоматическом режиме с помощью скриптинга на JavaScript - Pre-Request Script (простите за тавтологию, постараюсь больше не использовать)
Простые примеры скрипта с Basic Authorization
Прежде чем перейти к скриптингу, нужно подумать - а где мы будем хранить логины-пароли? На ум приходят три вещи:
Переменные внутри коллекции (не безопасно)
Переменные внутри скрипта (еще хуже, сложно для поддержки еще к тому же)
Переменные внутри окружения Postman (то, что нужно)
Переменные внутри окружения безопасны за счет не только того, что они скрыты визуально (если вдруг попадут в скрин), но и при "поделиться коллекцией" вы не передаете 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}`);
Результат мы можем увидеть на следующем экране
Ниже - скрипты по взятию переменных из коллекции и из окружения
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 нет, однако меня в систему пускает. Почему? |
Авторизация и не нужна |
Сохранились куки, их нужно чистить перед запросом |
Тут, наверное, стоит пояснить - а что значит включенные (активные) \ отключенные заголовки? Объясняю - бывает так, что в одном запросе ты накидаешь несколько заголовков с одинаковым ключом, но разным значением, чтоб в какой-то момент подключать нужный. Вот как это выглядит визуально:
Прежде чем перейти к тому, как ответил я, давайте разбираться со всем в коде:
Хм, как получить значение login-password из переменных окружения?
// Получение переменных из окружения
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)
}
Хм, а как посмотреть отключенные заголовки Authorization?
// Наименование (ключ) заголовка
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");
-
А как чистить куки? А вот это интересный вопрос, потому что:
-
Код выглядит просто:
// Очищаем cookies перед выполнением всех шагов const var_cookies = pm.cookies.jar(); var_cookies.clear(pm.request.url, function (error) { // error - <Error> });
Но перед этим нужно добавить сайт в Domains Allowlist (без http/https)
-
Отлично, основные моменты кода вспомнили, теперь давайте определяться с последовательностью действий с учетом вопросов \ нюансов.
Перед запросом очищаем все куки
Берем активные заголовки Authorization без учета регистра
Если их больше 1 - выдаем ошибку, прерываем работу скрипта и запроса
Если их 0 - значит так и задумано пользователем, просто отправляем запрос дальше
-
Если их 1 - обновляю только в случае, если в значении встречаются <*calc*>, где * - любой символ и не чувствителен к регистру
<Calculated By Script> - заменится значение, т.к. встречаются все символы
<CALC> - тоже заменится. Напомню, что ищем не чувствительное к регистру
<Any> - Не заменится
Symbol<calc> - не заменится, т.к. символ "<" не идет первым
<calc>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 - <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:
И, конечно, я добился того, ради чего все это затевалось, а именно я просто сохранил результат отправки запроса без боязни случайно "спалить" свой логин и пароль
Прошу вас, критикуйте, предлагайте, очень буду рад слышать мнение всех вас, золотых моих, умных, чтоб стать и самому лучше и сообществу помочь :-)