«1. Обзор

В этом кратком руководстве мы собираемся показать, как мы можем добавить функцию выхода из системы в приложение OAuth Spring Security.

Мы, конечно же, будем использовать приложение OAuth, описанное в предыдущей статье — Создание REST API с помощью OAuth2.

Примечание. В этой статье используется устаревший проект Spring OAuth. Версию этой статьи, использующую новый стек Spring Security 5, можно найти в нашей статье «Выход из системы в защищенном приложении OAuth».

2. Удаление маркера доступа

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

В реализации на основе JdbcTokenStore это означает удаление токена из TokenStore.

Давайте реализуем операцию удаления токена. Здесь мы будем использовать структуру URL-адреса parimary /oauth/token и просто введем для нее новую операцию DELETE.

Теперь, поскольку мы на самом деле используем URI /oauth/token здесь, нам нужно обращаться с ним осторожно. Мы не сможем просто добавить это к любому контроллеру — потому что в фреймворке уже есть операции, сопоставленные с этим URI — с POST и GET.

Вместо этого нам нужно определить, что это @FrameworkEndpoint, чтобы он подхватывался и разрешался с помощью FrameworkEndpointHandlerMapping вместо стандартного RequestMappingHandlerMapping. Таким образом, мы не столкнемся с частичными совпадениями и конфликтами:

@FrameworkEndpoint
public class RevokeTokenEndpoint {

    @Resource(name = "tokenServices")
    ConsumerTokenServices tokenServices;

    @RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
    @ResponseBody
    public void revokeToken(HttpServletRequest request) {
        String authorization = request.getHeader("Authorization");
        if (authorization != null && authorization.contains("Bearer")){
            String tokenId = authorization.substring("Bearer".length()+1);
            tokenServices.revokeToken(tokenId);
        }
    }
}

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

3. Удаление токена обновления

В предыдущей статье об обработке токена обновления мы настроили наше приложение так, чтобы оно могло обновлять токен доступа с помощью токена обновления. В этой реализации используется прокси-сервер Zuul с CustomPostZuulFilter для добавления значения refresh_token, полученного от сервера авторизации, в файл cookie refreshToken.

При отзыве маркера доступа, как показано в предыдущем разделе, маркер обновления, связанный с ним, также становится недействительным. Однако файл cookie httpOnly останется установленным на клиенте, учитывая, что мы не можем удалить его с помощью JavaScript, поэтому нам нужно удалить его со стороны сервера.

Давайте улучшим реализацию CustomPostZuulFilter, которая перехватывает URL-адрес /oauth/token/revoke, чтобы он удалял файл cookie refreshToken при обнаружении этого URL-адреса:

@Component
public class CustomPostZuulFilter extends ZuulFilter {
    //...
    @Override
    public Object run() {
        //...
        String requestMethod = ctx.getRequest().getMethod();
        if (requestURI.contains("oauth/token") && requestMethod.equals("DELETE")) {
            Cookie cookie = new Cookie("refreshToken", "");
            cookie.setMaxAge(0);
            cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
            ctx.getResponse().addCookie(cookie);
        }
        //...
    }
}

4. Удалите токен доступа из клиента AngularJS

Помимо отзыва токена доступа из хранилища токенов, файл cookie access_token также необходимо будет удалить со стороны клиента.

Давайте добавим в наш контроллер AngularJS метод, который очищает файл cookie access_token и вызывает сопоставление /oauth/token/revoke DELETE:

$scope.logout = function() {
    logout($scope.loginData);
}
function logout(params) {
    var req = {
        method: 'DELETE',
        url: "oauth/token"
    }
    $http(req).then(
        function(data){
            $cookies.remove("access_token");
            window.location.href="login";
        },function(){
            console.log("error");
        }
    );
}

Эта функция будет вызываться при нажатии на ссылку выхода:

<a class="btn btn-info" href="#" ng-click="logout()">Logout</a>

~ ~~ 5. Заключение

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

Полный исходный код примеров можно найти на GitHub.