Автоматическое получение access token в postman
Postman отличный инструмент для работы с REST API. Его возможности очень широки: от простой проверки эндпоинта, до написания полноценных тестов для API. Вы можете на основе ваших запросов замокать сервер или создать коллекцию и поделиться ею со всей командой. Так как сейчас очень популярна авторизация пользователя по токенам, то при работе это порождает некоторые неудобства. Так как правило, пользователю отдается 2 токена: access_token
и refresh_token
. И необходимый для авторизации access_token очень быстро протухает. И приходится, как-то добывать новый, зачастую просто вручную, отправляя запрос из другой вкладки. Это очень неудобно и просто замедляет работу. К счастью, у Postman существует отличный механизм, позволяющий полностью автоматизировать это действие.
Postman имеет механизм выполнения js скриптов перед запросом, с помощью которых мы можем выполнять необходимые нам действия.
К примеру, у нас есть endpont 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’а просто выполнять его обновление. В таком случае у нас может быть 3 варианта:
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 }
]);
}
Как видно из кода, дополнительно сохраня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
:
После этого все сохраняем и на этом все. Можно выполнять любые запросыб подставляя вместо 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: {
'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();
}