Postman отличный инструмент для работы с REST API. Его возможности очень широки: от простой проверки эндпоинта, до написания полноценных тестов для API. Вы можете на основе ваших запросов замокать сервер или создать коллекцию и поделиться ею со всей командой. Так как сейчас очень популярна авторизация пользователя по токенам, то при работе это порождает некоторые неудобства. Так как правило, пользователю отдается 2 токена: access_token и refresh_token. И необходимый для авторизации access_token очень быстро протухает. И приходится, как-то добывать новый, зачастую просто вручную, отправляя запрос из другой вкладки. Это очень неудобно и просто замедляет работу. К счастью, у Postman существует отличный механизм, позволяющий полностью автоматизировать это действие.

Postman имеет механизм выполнения js скриптов перед запросом, с помощью которых мы можем выполнять необходимые нам действия.

К примеру, у нас есть endpont http://localhost:63001/api/user и мы хотим получить с него данные. Но при попытке просто отправить, запрос получаем вполне логичную ошибку 401 Unauthorized:

Ошибка ‘401 Unauthorized’

Логичную, так как мы не отправили заголовок Authorization с токеном, в моем случае это Bearer token. Для этого выставляем соответствующий метод авторизации на вкладке Auth и заполняем поля:

Вкладка ‘Authorization’

И все получилось:

Успешный ответ

Мы проверили данные, решили послать еще один запрос и:

Повторная ошибка ‘401 Unauthorized’

Опять 401 Unauthorized. И постоянно обновлять токен это очень неудобно и утомляет, особенно если токен живет буквально пару минут. К счастью, решение этой проблемы кроется на вкладке Pre-req. Добавив туда строку console.log("Pre-request");

Вкладка ‘Pre-req’

Для наглядной работы откроем Postman console, кликнув по кнопке внизу:

Окно ‘Postman console’

После чего выполним запрос еще раз, и в консоли видно, что написанный нами код выполнился до отправки запроса:

Вывод в окно консоли

Таким образом, мы можем поместить туда код, который и добудет нам заветный токен. Сразу там же можем найти сниппет на отправку запроса, который вставит следующий код:

console.log("Pre-request");
pm.sendRequest("https://postman-echo.com/get", function (err, response) {
    console.log(response.json());
});

Выполнив запрос с этим кодом в консоли, можем увидеть, что перед выполнением основного запроса был выполнен GET запрос на https://postman-echo.com/get

Вывод в консоль 2 запросов

Теперь попробуем написать код, который получит access token:

const request = {
    url: 'http://localhost:63001/api/token',
    method: 'POST',
    header: 'application/x-www-form-urlencoded',
    body: {
        mode: 'formdata',
        formdata: [
            { key: 'grant_type', value: 'password' },
            { key: 'username', value: 'admin' },
            { key: 'password', value: 'password123' }
        ]
    }
};
pm.sendRequest(request, (err, res) => {
    const { access_token } = res.json();
    pm.globals.set('ACCESS_TOKEN', access_token);
    console.log('ACCESS_TOKEN: ${access_token}');
});

Как видим, в ответе мы можем получить все необходимые данные и записать напрямую заголовок через pm.request. Но у postman есть более элегантный метод для этого. Для его реализации в строке pm.globals.set('ACCESS_TOKEN', access_token); глобальной переменной ACCESS_TOKEN присваивается значение полученного access token’а. После чего в любом месте мы можем получить это значение, используя конструкцию { { ACCESS_TOKEN } } . Например, перейдя на вкладку Auth и введя вместо токена строку { { ACCESS_TOKEN } }. Притом при наведении мы даже можем видеть первоначальное и текущее значение переменной, если она уже определена.

Пример access token’а в окне Postman

После этого при выполнении следующего запроса ACCESS_TOKEN уже будет определен и запрос выполнится успешно. Но такой код будет выполняться намного дольше, так как на каждый запрос будет выполняться еще один для авторизации. К счастью, это можно избежать, просто написав логику по проверке токена. Так же логичным будет хранить и refresh token и при истечении access token’а просто выполнять его обновление. В таком случае у нас может быть 3 варианта:

  1. ACCESS_TOKEN и REFRESH_TOKEN отсутствуют - в этом случае следует авторизоваться в системе
  2. ACCESS_TOKEN и REFRESH_TOKEN определены, но ACCESS_TOKEN уже невалиден - в этом случае необходимо просто получить новый ACCESS_TOKEN
  3. ACCESS_TOKEN и REFRESH_TOKEN определены и валидны. В этом случае никаких дополнительный действий не требуется.

Сразу для упрощения написания определим небольшие функции для работы с глобальными переменными, чтобы не захламлять код:

const value = (key, defaultVlue) => pm.globals.get(key) || defaultVlue;
const setValue = (key, value) => pm.globals.set(key, value);

После этого начнем с того, что определим функцию для отправки запроса на авторизацию и сохранения результата:

const getTimeStamp = (expires_in = 0) => {
    const expiryDate = new Date();
    expiryDate.setSeconds(expiryDate.getSeconds() + expires_in);
    return expiryDate.getTime();
}

const sendRequest = (urlencoded) => {
    const request = {
        url: 'http://localhost:63001/api/token',
        method: 'POST',
        header: {
            'contetn-type': 'application/x-www-form-urlencoded'
        },
        body: { mode: 'urlencoded', urlencoded }
    };
    pm.sendRequest(request, (err, res) => {
        const { access_token, refresh_token, expires_in } = res.json();
        setValue('ACCESS_TOKEN', access_token);
        setValue('REFRESH_TOKEN', refresh_token);
        setValue('ACCESS_TOKEN_EXPIRY', getTimeStamp(expires_in));
    });
}

const sendLoginRequest = () => {
    const username = pm.environment.get('AUTH_USERNAME') || 'admin';
    const password = pm.environment.get('AUTH_PASSWORD') || 'password123';
    console.log('Send login request');
    sendRequest([
        { key: 'grant_type', value: 'password' },
        { key: 'username', value: username },
        { key: 'password', value: password }
    ]);
}

Как видно из кода, дополнительно сохраняeтся еще значение ACCESS_TOKEN_EXPIRY, это переменная, позволяющая определить заэкспайрен ли наш токен или нет до вызова запроса. Для этого просто прибавляем к текущему времени количество секунд указанное в поле expires_in. Обратите внимание, что значения AUTH_USERNAME и AUTH_PASSWORD берутся не из global, а из environment. Это сделано для простоты смены пользователя при выполнении запроса. Достаточно просто определить эти переменные в окружении, и без дополнительных настроек вы будете заходить в систему под своим пользователем.

Далее остаётся случай, когда у нас уже есть ACCESS_TOKEN и REFRESH_TOKEN, но первый из них устарел. Для этого мы определи еще одну функцию sendRefreshTokenRequest:

const sendRefreshTokenRequest = () => {
    const refreshToken = value('REFRESH_TOKEN');
    console.log('Send refresh token request');
    sendRequest([
        { key: 'grant_type', value: 'refresh_token' },
        { key: 'refresh_token', value: refreshToken }
    ]);
}

И осталось только применить эти функции:

const token = value('ACCESS_TOKEN');
const accessTokenExpiry = value('ACCESS_TOKEN_EXPIRY');

if(!token) {
    sendLoginRequest();
} else if(accessTokenExpiry <= getTimeStamp()) {
    sendRefreshTokenRequest();
}

И вот,наконец, все работает. Очистим глобальные переменные и выполним череду запросов, чтобы посмотреть, как выполняeт,ся наш код, когда и куда идут запросы:

Пример выполнения череды запросов с автоматическим обновлением токена

Как видно, сперва идет запрос на авторизацию пользователся, иными словами, вызыватся метод sendLoginRequest(). За ним следует 6 успешных GET запросов на /api/user. И после истечения access token’а вызывается метод sendRefreshTokenRequest(), котороый обновляет нашу пару токенов.

Осталось только заставить этот код выполняться для всей коллекции, а не только для определенного запроса. Для этого открываем на редактирование коллекцию, в второй находится наш запрос:

Редактирование коллекции

И переносим написанный скприт в секцию Pre-request script. Так же будет не совсем правильным хардкодить URL по которому идет авторизация. Намного правильнее будет добавить в environments переменную HOST, в которой и хранить хостб на который необходимо обращаться. Это можно сделать на вкладке Variables. А в поле url запроса вставить конструкцию ${pm.environment.get('HOST')}/api/token:

Вкладка ‘Pre-request script’ для коллекции Вкладка ‘Variables’ для коллекции

После этого все сохраняем и на этом все. Можно выполнять любые запросыб подставляя вместо http://localhost:63001 { { HOST } } и не думая об авторизации. Если обращение к какому-либо эндпоинту не требует авторизации, для этого достойно просто выбрать тип авторизации на ‘Not Auth’:

Пример запроса с переменными ‘HOST’ и ‘ACCESS_TOKEN’

Полный код

const value = (key, defaultVlue) => pm.globals.get(key) || defaultVlue;
const setValue = (key, value) => pm.globals.set(key, value);
const getTimeStamp = (expires_in = 0) => {
    const expiryDate = new Date();
    expiryDate.setSeconds(expiryDate.getSeconds() + expires_in);
    return expiryDate.getTime();
}

const sendRequest = (urlencoded) => {
    const request = {
        url: `${pm.environment.get('HOST')}/api/token`,
        method: 'POST',
        header: {
            'contetn-type': 'application/x-www-form-urlencoded'
        },
        body: { mode: 'urlencoded', urlencoded }
    };
    pm.sendRequest(request, (err, res) => {
        const { access_token, refresh_token, expires_in } = res.json();
        setValue('ACCESS_TOKEN', access_token);
        setValue('REFRESH_TOKEN', refresh_token);
        setValue('ACCESS_TOKEN_EXPIRY', getTimeStamp(expires_in));
    });
}

const sendLoginRequest = () => {
    const username = pm.environment.get('AUTH_USERNAME') || 'admin';
    const password = pm.environment.get('AUTH_PASSWORD') || 'password123';
    console.log('Send login request');
    sendRequest([
        { key: 'grant_type', value: 'password' },
        { key: 'username', value: username },
        { key: 'password', value: password }
    ]);
}

const sendRefreshTokenRequest = () => {
    const refreshToken = value('REFRESH_TOKEN');
    console.log('Send refresh token request');
    sendRequest([
        { key: 'grant_type', value: 'refresh_token' },
        { key: 'refresh_token', value: refreshToken }
    ]);
}

const token = value('ACCESS_TOKEN');
const accessTokenExpiry = value('ACCESS_TOKEN_EXPIRY');

if(!token) {
    sendLoginRequest();
} else if(accessTokenExpiry <= getTimeStamp()) {
    sendRefreshTokenRequest();
}

Ссылки на источники

  1. Execution order of scripts
  2. Pre request scripts
  3. Postman Sandbox API reference