1. Обзор
В этом кратком руководстве мы собираемся показать, как мы можем добавить функцию выхода из системы в приложение OAuth Spring Security .
Мы увидим несколько способов сделать это. Сначала мы увидим, как выйти из системы нашего пользователя Keycloak из приложения OAuth, как описано в разделе « Создание REST API с помощью OAuth2» , а затем с помощью прокси-сервера Zuul, который мы видели ранее .
Мы будем использовать стек OAuth в Spring Security 5. Если вы хотите использовать устаревший стек Spring Security OAuth, ознакомьтесь с этой предыдущей статьей: Выход из защищенного приложения OAuth (используя устаревший стек) .
2. Выход с помощью внешнего приложения
Поскольку маркеры доступа управляются сервером авторизации, их необходимо аннулировать на этом уровне. Точные шаги для этого будут немного отличаться в зависимости от используемого сервера авторизации.
В нашем примере, согласно документации Keycloak , для выхода непосредственно из приложения браузера мы можем перенаправить браузер на http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout ?redirect_uri=encodedRedirectUri
.
Наряду с отправкой URI перенаправления нам также необходимо передать id_token_hint
конечной точке выхода Keycloak . Это должно содержать закодированное значение id_token
.
Вспомним, как мы сохранили access_token
, аналогично сохраним и id_token
:
saveToken(token) {
var expireDate = new Date().getTime() + (1000 * token.expires_in);
Cookie.set("access_token", token.access_token, expireDate);
Cookie.set("id_token", token.id_token, expireDate);
this._router.navigate(['/']);
}
Важно отметить, что для получения ID Token в полезной нагрузке ответа сервера авторизации мы должны включить openid
в параметр scope .
Теперь давайте посмотрим процесс выхода из системы в действии.
Мы изменим нашу функцию выхода из системы
в Службе приложений
:
logout() {
let token = Cookie.get('id_token');
Cookie.delete('access_token');
Cookie.delete('id_token');
let logoutURL = "http://localhost:8083/auth/realms/foreach/protocol/openid-connect/logout?
id_token_hint=" + token + "&post_logout_redirect_uri=" + this.redirectUri;
window.location.href = logoutURL;
}
Помимо перенаправления, нам также необходимо сбросить токены доступа и идентификатора , которые мы получили от сервера авторизации.
Следовательно, в приведенном выше коде мы сначала удалили токены, а затем перенаправили браузер на API выхода
Keycloak .
Примечательно, что мы передали URI перенаправления как http://localhost:8089/
— тот, который мы используем во всем приложении, — поэтому мы окажемся на целевой странице после выхода из системы.
Удаление токенов доступа, идентификатора и обновления, соответствующих текущему сеансу, выполняется на стороне сервера авторизации. В этом случае наше браузерное приложение вообще не сохранило Refresh Token.
3. Выйти с помощью Zuul Proxy
В предыдущей статье об обработке токена обновления мы настроили наше приложение так, чтобы оно могло обновлять токен доступа с помощью токена обновления. В этой реализации используется прокси-сервер Zuul с настраиваемыми фильтрами.
Здесь мы увидим, как добавить функциональность выхода из системы к вышеперечисленному.
На этот раз мы будем использовать другой API Keycloak для выхода пользователя из системы. Мы будем вызывать POST в конечной точке выхода
из системы , чтобы выйти из сеанса с помощью вызова без браузера , вместо перенаправления URL-адреса, которое мы использовали в предыдущем разделе.
3.1. Определить маршрут для выхода
Для начала добавим еще один маршрут к прокси в наш application.yml
:
zuul:
routes:
//...
auth/refresh/revoke:
path: /auth/refresh/revoke/**
sensitiveHeaders:
url: http://localhost:8083/auth/realms/foreach/protocol/openid-connect/logout
//auth/refresh route
По сути, мы добавили подмаршрут к уже существующему auth/refresh
. Важно, чтобы мы добавили подмаршрут перед основным маршрутом, иначе Zuul всегда будет отображать URL-адрес основного маршрута .
Мы добавили подмаршрут вместо основного, чтобы иметь доступ к cookie- файлу refreshToken
только для HTTP , который был настроен на очень ограниченный путь как /auth/refresh
(и его подпути). Мы увидим, зачем нам нужен файл cookie, в следующем разделе.
3.2. POST в /logout сервера авторизации
Теперь давайте улучшим реализацию CustomPreZuulFilter
, чтобы перехватывать URL-адрес /auth/refresh/revoke
и добавлять необходимую информацию для передачи на сервер авторизации.
Параметры формы, необходимые для выхода, аналогичны параметрам запроса Refresh Token , за исключением того, что Grant_type
отсутствует :
@Component
public class CustomPostZuulFilter extends ZuulFilter {
//...
@Override
public Object run() {
//...
if (requestURI.contains("auth/refresh/revoke")) {
String cookieValue = extractCookie(req, "refreshToken");
String formParams = String.format("client_id=%s&client_secret=%s&refresh_token=%s",
CLIENT_ID, CLIENT_SECRET, cookieValue);
bytes = formParams.getBytes("UTF-8");
}
//...
}
}
Здесь мы просто извлекли файл cookie refreshToken
и отправили необходимые formParams.
3.3. Удалить токен обновления
При отзыве токена доступа с использованием перенаправления выхода
из системы , как мы видели ранее, токен обновления, связанный с ним, также становится недействительным сервером авторизации.
Однако в этом случае файл cookie httpOnly
останется установленным на клиенте. Учитывая, что мы не можем удалить его через JavaScript, нам нужно удалить его со стороны сервера.
Для этого добавим в реализацию CustomPostZuulFilter
, которая перехватывает URL-адрес /auth/refresh/revoke
, чтобы удалить файл cookie refreshToken
при обнаружении этого URL-адреса:
@Component
public class CustomPostZuulFilter extends ZuulFilter {
//...
@Override
public Object run() {
//...
String requestMethod = ctx.getRequest().getMethod();
if (requestURI.contains("auth/refresh/revoke")) {
Cookie cookie = new Cookie("refreshToken", "");
cookie.setMaxAge(0);
ctx.getResponse().addCookie(cookie);
}
//...
}
}
3.4. Удалить токен доступа из клиента Angular
Помимо отзыва маркера обновления, файл cookie access_token
также необходимо удалить со стороны клиента.
Давайте добавим в наш контроллер Angular метод, который очищает файл cookie access_token
и вызывает отображение POST /auth/refresh/revoke :
logout() {
let headers = new HttpHeaders({
'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'});
this._http.post('auth/refresh/revoke', {}, { headers: headers })
.subscribe(
data => {
Cookie.delete('access_token');
window.location.href = 'http://localhost:8089/';
},
err => alert('Could not logout')
);
}
Эта функция будет вызываться при нажатии на кнопку Logout:
<a class="btn btn-default pull-right"(click)="logout()" href="#">Logout</a>
4. Вывод
В этом кратком, но подробном руководстве мы показали, как мы можем выйти из системы пользователя из защищенного приложения OAuth
и аннулировать токены этого пользователя.
Полный исходный код примеров можно найти на GitHub .