Подтвердить что ты не робот

Недопустимый токен XSRF в/oauth/токен

Полный код для реализации Spring OAuth2 многофакторной аутентификации был загружен на сайт для обмена файлами по этой ссылке. Инструкции приведены ниже, чтобы воссоздать текущую проблему на любом компьютере всего за несколько минут. Предлагается 500 баллов.

<ч/" > ТЕКУЩАЯ ПРОБЛЕМА:


Большинство алгоритмов аутентификации работают правильно. Программа не прерывается до самого конца потока управления, показанного ниже. В частности, в конце SECOND PASS появляется ошибка Invalid CSRF token found for http://localhost:9999/uaa/oauth/token ниже. Приложение в приведенной выше ссылке было разработано путем добавления пользовательских OAuth2RequestFactory, TwoFactorAuthenticationFilter и TwoFactorAuthenticationController к authserver app этот Spring Пример загрузки OAuth2 GitHub. Какие конкретные изменения необходимо внести в код ниже, чтобы решить эту ошибку токена CSRF и включить двухфакторную аутентификацию?

Мои исследования заставляют меня подозревать, что CustomOAuth2RequestFactory (API по этой ссылке) может быть местом для настройки решения, поскольку оно определяет способы управления AuthorizationRequest и TokenRequest s.

Этот раздел официальной спецификации OAuth2 указывает, что параметр state запроса, сделанного для конечной точки авторизации, является место, где добавлен токен csrf.

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

<ч/" > КОНТРОЛЬНЫЙ ПОТОК ОКРУЖАЮЩАЯ ОШИБКА:


Текущая ошибка генерируется во время SECOND PASS через TwoFactorAuthenticationFilter в блок-схеме ниже. Все работает по назначению, пока поток управления не попадет в SECOND PASS.

Следующая блок-схема иллюстрирует поток управления процессом двухфакторной аутентификации, который используется кодом в загружаемом приложении.

2_FA_OAuth_Flow.gif

В частности, заголовки Firefox HTTP для последовательности POST и GET показывают, что один и тот же куки файл XSRF отправляется с каждым запросом в последовательности. Значения токенов XSRF не вызывают проблемы до тех пор, пока после POST /secure/two_factor_authentication, который запускает обработку сервера на конечных точках /oauth/authorize и /oauth/token, при этом /oauth/token бросает ошибку Invalid CSRF token found for http://localhost:9999/uaa/oauth/token.

Чтобы понять взаимосвязь между приведенной выше схемой управления и конечными точками /oauth/authorize и /oauth/token, вы можете сравнить приведенную выше блок-схему рядом с диаграмму для потока отдельных факторов в официальной спецификации в отдельном окне браузера. Выше SECOND PASS просто повторяет шаги из однофакторной официальной спецификации во второй раз, но с большими разрешениями во время SECOND PASS.

<ч/" >
КАКИЕ СЛОВА:


Заголовки запроса и ответа HTTP указывают, что:

1.) POST до 9999/login с правильными username и password представленными результатами в перенаправлении на 9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v, за которым следует GET 9999/secure/two_factor_authenticated. Один токен XSRF остается постоянным на этих биржах.

2.) POST до 9999/secure/two_factor_authentication с правильным пин-кодом отправляет один и тот же маркер XSRF и успешно перенаправляется на POST 9999/oauth/authorize и превращает его в TwoFactorAuthenticationFilter.doFilterInternal() и переходит к request 9999/oauth/token, но 9999/oauth/token отклоняет запрос, потому что тот же старый токен XSRF не соответствует новому значению токена XSRF, которое, по-видимому, было создано во время ПЕРВЫЙ ПАСС.

Одно очевидное различие между 1.) и 2.) заключается в том, что второй request 9999/oauth/authorize в 2.) не содержит параметры url, которые включены в первый запрос 9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v в 1.), а также определены в официальная спецификация. Но неясно, вызвало ли это проблему.

Также неясно, как получить доступ к параметрам для отправки полностью сформированного запроса из TwoFactorAuthenticationController.POST. Я сделал SYSO parameters Map в HttpServletRequest для метода контроллера POST 9999/secure/two_factor_authentication, и все, что он содержит, это переменные pinVal и _csrf.

Вы можете прочитать все заголовки HTTP и Spring Журналы загрузки на сайте обмена файлами щелкнув по этой ссылке.

<ч/" > НЕПРЕРЫВНЫЙ ПОДХОД:


Я попробовал подход @RobWinch к аналогичной проблеме в среде Spring Security 3.2, но этот подход, похоже, не относится к контексту Spring OAuth2. В частности, когда следующий код кода XSRF обновляется в коде TwoFactorAuthenticationFilter, показанном ниже, заголовки заголовков нисходящего потока показывают другое/новое значение токена XSRF, но эта же ошибка возникает.

if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
    CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
    response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}

Это означает, что конфигурация XSRF должна быть обновлена ​​таким образом, чтобы /oauth/authorize и /oauth/token могли разговаривать друг с другом и с клиентскими и ресурсными приложениями успешно управлять XSRF значения токена. Возможно, CustomOAuth2RequestFactory - это то, что нужно изменить, чтобы выполнить это. Но как?

<ч/" > СООТВЕТСТВУЮЩИЙ КОД:


Код CustomOAuth2RequestFactory:

public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {

    public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";

    public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
        super(clientDetailsService);
    }

    @Override
    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session = attr.getRequest().getSession(false);
        if (session != null) {
            AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
            if (authorizationRequest != null) {
                session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
                return authorizationRequest;
            }
        }

        return super.createAuthorizationRequest(authorizationParameters);
    }
}

Код TwoFactorAuthenticationFilter:

//This class is added per: https://stackoverflow.com/info/30319666/two-factor-authentication-with-spring-security-oauth2
/**
 * Stores the oauth authorizationRequest in the session so that it can
 * later be picked by the {@link com.example.CustomOAuth2RequestFactory}
 * to continue with the authoriztion flow.
 */
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    private OAuth2RequestFactory oAuth2RequestFactory;
    //These next two are added as a test to avoid the compilation errors that happened when they were not defined.
    public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
    public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";

    @Autowired
    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
    }

    private boolean twoFactorAuthenticationEnabled(Collection<? extends GrantedAuthority> authorities) {
        System.out.println(">>>>>>>>>>> List of authorities includes: ");
        for (GrantedAuthority authority : authorities) {
            System.out.println("auth: "+authority.getAuthority() );
        }
        return authorities.stream().anyMatch(
            authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
        );
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
        // Check if the user hasn't done the two factor authentication.
        if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
        System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
        AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
            /* Check if the client authorities (authorizationRequest.getAuthorities()) or the user ones
               require two factor authenticatoin. */
        System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
        System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
        if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
                twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
                // Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
                // to return this saved request to the AuthenticationEndpoint after the user successfully
                // did the two factor authentication.
                request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);

                // redirect the the page where the user needs to enter the two factor authentiation code
                redirectStrategy.sendRedirect(request, response,
                    ServletUriComponentsBuilder.fromCurrentContextPath()
                        .path(TwoFactorAuthenticationController.PATH)
                        .toUriString());
                return;
            }
        }
        //THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
        //if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
        //    CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
            // this is the value of the token to be included as either a header or an HTTP parameter
        //    response.setHeader("XSRF-TOKEN", token.getToken());
        //}

        filterChain.doFilter(request, response);
    }

    private Map<String, String> paramsFromRequest(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            params.put(entry.getKey(), entry.getValue()[0]);
        }
        return params;
    }
}

RE-СОЗДАНИЕ ПРОБЛЕМЫ НА КОМПЬЮТЕРЕ:


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

1.) Загрузите сжатую версию приложения с сайта совместного доступа к файлам, нажав на эту ссылку.

2.) Разархивируйте приложение, набрав: tar -zxvf oauth2.tar(2).gz

3.) запустите приложение authserver, перейдя на oauth2/authserver, а затем введите mvn spring-boot:run.

4.) запустите приложение resource, перейдя на oauth2/resource, а затем введите mvn spring-boot:run

5.) запустите приложение ui, перейдя на oauth2/ui, а затем введите mvn spring-boot:run

6.) Откройте веб-браузер и перейдите к http : // localhost : 8080

7.) Нажмите Login, а затем введите Frodo в качестве пользователя и MyRing в качестве пароля и нажмите, чтобы отправить.

8.) Введите 5309 в качестве Pin Code и нажмите "Отправить". Это вызовет ошибку, показанную выше.

Вы можете просмотреть полный исходный код:

a.) импортируя проекты maven в вашу среду IDE или

b.), перемещаясь по распакованным каталогам и открывая текстовый редактор.

<ч/" > Вы можете прочитать все заголовки HTTP и Spring Журналы загрузки на сайте обмена файлами щелкнув по этой ссылке.

4b9b3361

Ответ 1

Одна идея, которая появилась у меня в голове:

Если активирована фиксация сеанса, новый сеанс создается после успешного аутентификации пользователя (см. SessionFixationProtectionStrategy). Это также, конечно, создаст новый токен csrf, если вы используете HttpSessionCsrfTokenRepository по умолчанию. Поскольку вы упоминаете заголовок XSRF-TOKEN, я предполагаю, что вы используете интерфейс JavaScript. Я мог представить, что оригинальный токен csrf, который использовался для входа, хранится и повторно используется впоследствии - это не сработает, потому что этот токен csrf больше недействителен.

Вы можете попробовать отключить фиксацию сеанса (http.sessionManagement().sessionFixation().none() или <session-management session-fixation-protection="none"/>) или повторно получить текущий токен CSRF после входа в систему.

Ответ 2

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

@Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    HttpSession session = attr.getRequest().getSession(false);
    if (session != null) {
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
        if (authorizationRequest != null) {
            session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
//UPDATE THE STATE VARIABLE WITH THE NEW TOKEN.  THIS PART IS NEW
            CsrfToken csrf = (CsrfToken) attr.getRequest().getAttribute(CsrfToken.class.getName());
            String attrToken = csrf.getToken();
            authorizationRequest.setState(attrToken);                

            return authorizationRequest;
        }
    }

    return super.createAuthorizationRequest(authorizationParameters);
}

Я пересматриваю это, потому что мой первоначальный проект ответа был опущен. Эта версия идет по тому же пути, который, я считаю, является правильным средством подхода.