Автоматическое получение access token в postman
Postman отличный инструмент для работы с REST API, обладающий широким спектром возможностей,
начиная от простой проверки эндпоинта и заканчивая написанием полноценных тестов для API.
С его помощью можно замокать сервер на основе запросов или создать коллекцию и легко поделиться ею со всей командой.
В современных приложениях часто используется авторизация пользователей по токенам, что может вызывать определенные неудобства при работе.
Как правило, пользователю предоставляются два токена: access_token
и refresh_token
,
причем access_token
быстро устаревает, требуя обновления. В таких случаях приходится вручную выполнять запрос на получение
нового токена из другой вкладки, что сильно замедляет работу и ухудшает пользовательский опыт.
К счастью,у Postman есть отличный механизм, который позволяет полностью автоматизировать этот процесс.
Postman обладает механизмом выполнения JavaScript-скриптов перед отправкой запроса, что позволяет нам выполнять необходимые действия.
Например, у нас есть endpoint http://localhost:63001/api/user
, и мы хотим получить данные с него. Но при попытке отправки запроса получаем вполне логичную ошибку 401 Unauthorized
.
Логично, поскольку мы не включили заголовок Authorization
с токеном, который в моем случае является Bearer token.
Для этого мы устанавливаем соответствующий метод авторизации на вкладке Auth и заполняем соответствующие поля.
И все получилось:
Мы проверили данные, решили послать еще один запрос и:
Опять же, получаем ошибку 401 Unauthorized
. Постоянное обновление токена очень неудобно и утомительно, особенно если токен действует всего несколько минут.
К счастью, решение этой проблемы находится на вкладке Pre-req. Добавив туда строку console.log("Pre-request")
Для наглядности откроем 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
Теперь попробуем написать код, который получит 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
уже будет определен, и запрос выполнится успешно.
Однако такой код будет выполняться намного дольше, так как на каждый запрос будет выполняться еще один для авторизации.
К счастью, этого можно избежать, просто написав логику проверки токена. Также логичным будет хранить refresh token и при истечении access token’а просто выполнять его обновление. В таком случае у нас может быть три варианта состояния:
ACCESS_TOKEN
иREFRESH_TOKEN
отсутствуют, в этом случае следует авторизоваться в системе.ACCESS_TOKEN
иREFRESH_TOKEN
определены, ноACCESS_TOKEN
уже невалиден, в этом случае необходимо просто получить новыйACCESS_TOKEN
.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 }
]);
}
Как видно из кода, дополнительно сохраняется значение ACCESS_TOKEN_EXPIRY
. Это переменная, позволяющая определить, истек ли срок действия нашего токена или нет до вызова запроса.
Для этого просто прибавляем к текущему времени количество секунд, указанное в поле expires_in
.
Обратите внимание, что значения AUTH_USERNAME
и AUTH_PASSWORD
берутся не из глобальных переменных, а из переменных окружения.
Это сделано для простоты смены пользователя при выполнении запроса.
Достаточно просто определить эти переменные в окружении, и без дополнительных настроек вы сможете входить в систему под своим пользователем.
Далее рассмотрим случай, когда у нас уже есть 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();
}
И вот, наконец, все работает. Очистим глобальные переменные и выполним серию запросов, чтобы посмотреть, как выполняется наш код, и куда направляются запросы:
Как видно, сперва отправляется запрос на авторизацию пользователя, то есть вызывается метод sendLoginRequest()
.
Затем следует 6 успешных GET-запросов на /api/user
. После истечения срока действия access token’а вызывается метод sendRefreshTokenRequest()
, который обновляет нашу пару токенов.
Для того чтобы этот код выполнялся для всей коллекции, а не только для определенного запроса, откроем на редактирование коллекцию, в которой находится наш запрос.
Переносим написанный скрипт в секцию Pre-request script. Также не совсем правильно хардкодить URL, по которому происходит авторизация. Более правильным будет добавить переменную HOST
в среду, где будет храниться хост, к которому нужно обращаться. Это можно сделать на вкладке Variables. А в поле URL
запроса вставить конструкцию ${pm.environment.get('HOST')}/api/token
:
После этого сохраняем все изменения. Теперь можно выполнять любые запросы, подставляя вместо http://localhost:63001
переменную {{HOST}}
, и не думая об авторизации. Если обращение к какому-либо эндпоинту не требует авторизации, просто выбираем тип авторизации Not Auth
.
Полный код
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: {
'content-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();
}