Статьи

Управление токенами OAuth 2.0 с помощью Stormpath и Spring Boot

Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!

Управление токенами OAuth 2.0 часто неправильно понимают и трудно реализовать правильно. К счастью, благодаря SDK и интеграции Stormpath мы делаем управление токенами легким — даже увлекательным. Этот 20-минутный учебник покажет вам, как реализовать управление токенами с помощью интеграции Stormpath Spring Boot и Spring Security.

Хотя Spring Security действительно имеет встроенную поддержку OAuth 2.0, в Spring Boot отсутствует встроенная поддержка управления токенами, и известно, что работа с протоколом OAuth вызывает спонтанные вспышки ульев, холодные поты и длительное «удаление лица».

face_desk

Интеграция Spring Boot в Stormpath поддерживает два потока OAuth: grant_type=password и grant_type=refresh_token . Тип предоставления пароля позволяет вам ввести имя пользователя и пароль и получить токен доступа и токен обновления. Тип предоставления обновленного токена позволяет вам передать обновленный токен и получить новый токен доступа.

Оба они доступны через одну конечную точку, /oauth/token , которая доступна из коробки с интеграцией Stormpath. Так что не бойся! Управление токенами Stormpath выполняет всю тяжелую работу. Вам просто нужно создать HTTP-запрос, используя некоторые основные правила, описанные ниже.

Ресурсы для этого урока

К концу этого поста у вас будет все, что нужно, чтобы поменять имя пользователя и пароль на набор токенов, который позволяет пользователю получать доступ к ограниченным ресурсам в приложении. Вы также сможете обновлять и отзывать токены, чтобы обеспечить точный контроль над тем, как пользователи получают доступ к вашему приложению и как долго они остаются в системе.

Код, который поддерживает этот пост, здесь .

Вы можете увидеть пример в действии по адресу: https://jquery-spa.herokuapp.com (из-за политики сна Heroku, возможно, вам придется подождать несколько секунд, чтобы приложение сначала отреагировало), и вы можете развернуть его на своем Аккаунт Heroku прямо сейчас с помощью кнопки ниже.

кнопка

Хотя управление токенами, как правило, является закулисным делом, в этом примере используется SPA или одностраничное приложение. Чтобы сохранить пример простым (и независимым от фреймворка Javascript), все вызовы выполняются с использованием функциональности jQuery ajax .

Современное управление токенами — доступ и обновление токенов

В современном приложении, которое поддерживает управление токенами OAuth 2.0, пользовательский сеанс обычно имеет один токен доступа с коротким сроком действия и один токен обновления с более длинным сроком действия. По истечении срока действия токена доступа приложение будет использовать токен обновления для получения нового токена доступа. Этот процесс повторяется до истечения срока действия маркера обновления. На этом этапе пользователю необходимо будет снова войти в приложение.

Чтобы глубже погрузиться в токены OAuth и как они работают, ознакомьтесь с постом моего коллеги Рэндалла , в котором подробно рассматривается OAuth .

JSON Web Token (JWT) Безопасность PSA

В спецификации OAuth 2.0 не указан конкретный формат токенов, поэтому Stormpath использует JWT для представления токенов доступа и токенов обновления. JWT имеют дополнительную информацию, закодированную в них, и, что более важно, они криптографически подписаны, чтобы предоставить убедительные доказательства того, что токен не был подделан.

Токены доступа Stormpath также предоставляют важный уровень дополнительной безопасности — они всегда содержат ссылку на связанный токен обновления. Эта ссылка является заявкой, идентифицируемой кодом rti . Это один из способов, которыми Stormpath обеспечивает использование токена доступа для доступа к защищенному ресурсу, а не обновления. Другие реализации OAuth 2.0 (непреднамеренно) позволили токену обновления действовать как токен доступа, предоставляя пользователям более длительный доступ, чем они должны иметь. Мы остерегаемся этого.

Если вы хотите узнать больше о JWT и о том, как их безопасно использовать, ознакомьтесь с нашими публикациями о том, как правильно использовать JWT , как создавать безопасные пользовательские интерфейсы с JWT и где хранить ваши JWT .

ОК, со всем этим, давайте начнем!

Конфигурация Spring Security

В следующих разделах мы рассмотрим пример приложения через призму вызовов jQuery, которые выполняются для конечной точки backend /oauth/token в приложении Spring Boot.

Прежде чем мы перейдем к этому, давайте посмотрим на конфигурацию Spring Security для этого примера приложения:

01
02
03
04
05
06
07
08
09
10
11
12
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.apply(stormpath()).and()
            .authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/decode").permitAll()
            .antMatchers("/static/**").permitAll();
    }
}

Интеграция Spring Security в Stormpath следует конфигурации по умолчанию для Spring Security, которая заключается в том, что все пути заблокированы.

В приведенной выше конфигурации мы разрешаем неаутентифицированный доступ к / и /decode а также к любому пути в /static . Мы увидим это в действии, работая с примером ниже.

Давайте возьмем несколько токенов

В нашем примере SPA есть два основных статических файла: home.html и static/js/token-management-guided-tour.js .

home.html — это шаблон Thymeleaf, в который загружается static/js/token-management-guided-tour.js файл Javascript static/js/token-management-guided-tour.js .

Вот представление о различных разделах home.html :

Дом 1

Он разбит на 7 шагов, и приложение проведет вас по примеру стиля «экскурсия».

Первым делом необходимо ввести адрес электронной почты и пароль и получить обратно токены доступа и обновления. Давайте посмотрим на это в действии.

Войти с помощью электронной почты и пароля

Вот код jQuery, который обрабатывает начальный запрос к конечной точке /oauth/token ( error и complete функции опущены для краткости):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
$('#login-form').submit(function (e) {
  
    var theForm = $('#login-form');
  
    $.ajax({
        type: 'post',
        url: '/oauth/token',
        data: theForm.serialize(),
        success: function (data) {
            accessToken = data.access_token;
            refreshToken = data.refresh_token;
            var accessTokenParts = accessToken.split('.');
            $.each(['header', 'payload', 'signature'], function (index, value) {
                $('#access-token-' + value).html(accessTokenParts[index]);
            });
            $('#login-error').hide();
            showOnlyDiv('access-token-div');
        }
    });
  
    e.preventDefault();
});

Строка 3 дает нам ручку к форме.

Согласно спецификации OAuth , метод будет POST а Content-Typeapplication/x-www-form-urlencoded (это значение по умолчанию для вызова jQuery $.ajax ). Данные, передаваемые в /oauth/token будут выглядеть так:

1
grant_type=password&username=<username>&password=<password>

Это результат вызова theForm.serialize() в строке 8.

Обработчик успеха, который начинается в строке 9, сохраняет access_token и refresh_token в локальных переменных. Он также отображает части access_token (заголовок, полезная нагрузка и подпись) в вашем браузере как визуальное представление JWT:

access_token

Примечание. Как показано на скриншоте, пример приложения предназначен только для демонстрации. В «реальной жизни» вы не захотите показывать токен доступа в браузере и использовать лучшие методы для хранения токенов в своем клиентском приложении. У нас есть отличный пост об этих лучших практиках здесь .

Анатомия Stormpath Access Token

В примере приложения, если вы нажмете кнопку Decode , вы увидите что-то вроде следующего:

декодированный

Это показывает заголовок и разделы полезной нагрузки JWT, которые являются токеном доступа. Чтобы понять, как мы декодировали наш APIController доступа, нам нужно взглянуть на метод APIController в APIController в примере приложения:

1
2
3
4
5
6
7
8
9
@RequestMapping("/decode")
public Jws<Claims> decode(@RequestParam String token) throws UnsupportedEncodingException {
  
    Jws<Claims> claims = Jwts.parser()
        .setSigningKey(client.getApiKey().getSecret().getBytes("UTF-8"))
        .parseClaimsJws(token);
  
    return claims;
}

Метод принимает JWT (который в данном случае является нашим токеном доступа) в качестве параметра и использует библиотеку JJWT для анализа заявок. Он также проверяет, что подпись JWT действительна как часть процесса синтаксического анализа. Для этого он использует секрет от клиента Stormpath, который был использован для создания JWT.

Наконец, метод возвращает Jws<Claims> . Именно здесь включается автоматический сопоставитель Jackson JSON Spring Boot и преобразует этот объект в JSON. Вот как выглядит необработанный ответ:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
{
    "header": {
        "kid": "R92SBHJC1U4DAIMGQ3MHOGVMX",
        "alg": "HS256"
    },
    "body": {
        "jti": "6UBPQ975cDDiz8ckHqWIZF",
        "iat": 1456242057,
        "exp": 1456245657,
        "rti": "6UBPQ5n0hcukMJWt5af1xB"
    },
    "signature": "Pm30FjdXOmx_fMGhfku85Z9xc6qE-EZgKHI4mV46KO8"
}

Обратите внимание, что body (полезная нагрузка) декодированного JWT включает ссылку на Счет Stormpath в sub (субъекте). Именно эта учетная запись используется при доступе к ограниченным ресурсам, как мы увидим далее.

Использование токена доступа

После декодирования токена доступа следующим шагом в нашем ознакомительном туре нашего примера приложения будет использование ограниченного ресурса с помощью этого токена доступа. Вот код jQuery, который выполняет это:

01
02
03
04
05
06
07
08
09
10
11
12
13
$('#restricted').click(function () {
  
    $('#account-info-table tbody').empty();
    $.ajax({
        type: 'get',
        url: '/restricted',
        success: function (data) {
            var newRowContent = '<tr><td>' + data.fullName + '</td><td>' + data.email + '</td></tr>';
            $('#account-info-table tbody').append(newRowContent);
            showOnlyDiv('account-info-div');
        }
    })
});

Вам может быть интересно, где аутентификация находится в приведенном выше коде. Куки-файлы были установлены в браузере на шагах, описанных выше, когда вы вошли в систему. Если вы посмотрите на инспектор в вашем браузере, вы увидите, что access_token передается в запрос к /restricted конечной точке. В отдельной сессии браузера вы можете попробовать нажать: http://localhost:8080/restricted . Вы будете перенаправлены на /login . Это функция нашей конфигурации Spring Security. Путь с /restricted не был одним из тех, которые мы явно разрешили в конфигурации Spring Security, и поэтому запрос должен быть аутентифицирован. Чтобы понять ответ от /restricted APIController , давайте посмотрим на метод с restricted в APIController :

1
2
3
4
5
@RequestMapping("/restricted")
public AccountInfo restricted(HttpServletRequest req) {
    Account account = AccountResolver.INSTANCE.getAccount(req);
    return new AccountInfo(account.getFullName(), account.getEmail());
}

Это просто возвращает AccountInfo модели AccountInfo который имеет полное имя и адрес электронной почты аутентифицированного пользователя. Объект AccountInfo определенный в примере приложения, легко конвертируется в JSON тем же автоматическим процессом сопоставления Spring Boot, который мы видели выше.

Приведенная выше функция success jQuery отображает информацию об учетной записи, возвращенную из запроса к /restricted .

Управление токенами: это освежает!

Следующим шагом в нашем путешествии является использование токена обновления для получения нового токена доступа. В типичном мобильном приложении за кадром происходит следующее, о чем пользователь даже не подозревает:

  1. Текущий токен доступа истекает
  2. Пользователь пытается получить доступ к ограниченной части приложения, используя (просроченный) токен доступа
  3. Приложение получает 401 (неавторизованный) код ответа HTTP
  4. Приложение использует токен обновления для получения нового токена доступа
  5. Приложение пытается получить доступ к ограниченной части приложения, используя новый токен доступа.
  6. Пользователь видит результат этого запроса без необходимости повторного входа

Примечание. Если срок действия маркера обновления истек, пользователю на шаге 4 потребуется снова войти в систему.

Stormpath предоставляет политику OAuth в консоли администратора, чтобы установить срок действия маркеров доступа и токенов обновления.

Взгляд на политику OAuth

Вот как выглядит политика OAuth в консоли администратора Stormpath:

oauth_policy

Срок действия по умолчанию для токенов доступа составляет 1 час, а по умолчанию TTL для токенов обновления — 60 дней.

Обновление токена доступа

Когда вы нажимаете кнопку « Refresh the Access Token в примере приложения, вы получаете ответ, который выглядит следующим образом:

обновление

Это отобразит новый токен доступа, полученный в процессе обновления. Чтобы понять, что там происходит, вернемся к нашему коду jQuery (обработчик успеха для краткости опущен):

01
02
03
04
05
06
07
08
09
10
$('#refresh').click(function () {
  
    $.ajax({
        type: 'post',
        url: '/oauth/token',
        data: 'grant_type=refresh_token&refresh_token=' + refreshToken,
        success: function (data) {
        }
    })
});

Еще раз, мы делаем HTTP POST для конечной точки /oauth/token . В отличие от grant_type=password , в этом случае мы используем grant_type=refresh_token и grant_type=refresh_token обновления, который мы сохранили ранее.

Ответом на этот вызов ajax является новый токен доступа, который затем отображается в браузере под старым токеном доступа.

Пните этот жетон на обочину

Последняя часть нашей радости по управлению токенами аннулирует токены доступа и обновления. Это достигается путем нажатия на (встроенную) конечную точку /logout предоставляющей токен доступа в качестве токена носителя, как мы делали при обращении к ограниченному ресурсу ранее. Давайте посмотрим на код jQuery, который выполняет это:

01
02
03
04
05
06
07
08
09
10
$('#revoke').click(function () {
  
    $.ajax({
        type: 'post',
        url: '/logout',
        success: function () {
            showOnlyDiv('revoke-div');
        }
    })
});

Мы отправляем HTTP-запрос POST в /logout . На этом access_token файлы cookie, содержащие access_token и refresh_token , удаляются, а токены аннулируются в бэкэнде Stormpath. Это обрабатывается автоматически за кулисами.

Приложение-пример снова пытается /restricted путь /restricted . Результат выглядит так:

потерпеть неудачу

Результат показывает, что попытка использовать удаленный токен доступа не будет работать.

В итоге

/oauth/token точка /oauth/token поставляемая «из коробки» в интеграции Spring Boot от Stormpath, обеспечивает все функциональные возможности современной системы управления токенами.

В этом посте мы рассмотрели, что такое токены Access и Refresh, а также как их получают, используют, обновляют и аннулируют.

Вы можете увидеть пример приложения, использованного в этом посте, по адресу: https://jquery-spa.herokuapp.com или кнопку ниже, чтобы развернуть приложение в своей учетной записи Heroku.

кнопка

Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!

Кнопка-знак вверх, теперь