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

Базовая авторизация через встроенный механизм 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

Результат выполнения скрипта по добавлению заголовка 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

Признак активности заголовка в 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

Шаг 1. Проваливаемся в Cookies

Шаг 2. Проваливаемся в Domains Allowlist

Шаг 2. Проваливаемся в Domains Allowlist

Шаг 3. Добавляем сайт в 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

Запрос не отправился, т.к. 2 активных заголовка Authorization

Отправка запроса без Authorization

Отправка запроса без Authorization

Результат работы скрипта. Заголовок нужный найден и заменен

Результат работы скрипта. Заголовок нужный найден и заменен

И, конечно, я добился того, ради чего все это затевалось, а именно я просто сохранил результат отправки запроса без боязни случайно «спалить» свой логин и пароль

Результат порадовал =)

Результат порадовал =)

Прошу вас, критикуйте, предлагайте, очень буду рад слышать мнение всех вас, золотых моих, умных, чтоб стать и самому лучше и сообществу помочь :-)

© Habrahabr.ru