14

Полный код для реализации аутентификации многофакторной пружины OAuth2 был загружен в a file sharing site at this link. Инструкции приведены ниже, чтобы воссоздать текущую проблему на любом компьютере всего за несколько минут. Предлагается 500 очков.Недопустимый токен XSRF в/oauth/token


ТОК ПРОБЛЕМА:


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

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

This section of the official OAuth2 spec указывает, что параметр запроса, сделанного в конечной точке авторизации state это место, где добавляется csrf маркер.

Кроме того, код в ссылке использует the Authorization Code Grant Type described at this link to the official spec, что означало бы, что стадия C в потоке не обновляет csrf код, таким образом, вызывая ошибку в шаге D. (Вы можете просмотреть весь поток, включая стадии C и Стадия D в the official spec)


УПРАВЛЕНИЯ ПОТОКА ОКРУЖЕНИЕ текущей ошибки:.


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

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

В частности, 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 конечных точек, вы можете сравнить вышеупомянутую сторону блок-схемы бок with the chart for the single factor flow at the official spec в отдельном окне браузера. Верхний номер 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, который, по-видимому, был создан во время FIRST PASS.

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

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

Вы можете прочитать все журналы заголовков HTTP и Spring Boot на сайте обмена файлами by clicking on this link.


неудавшийся ПОДХОД:


Я попытался @RobWinch's approach to a similar problem in the Spring Security 3.2 environment, но подход, кажется, не применяются к контексту 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/questions/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's authorities (authorizationRequest.getAuthorities()) or the user's 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; 
    } 
} 

воссоздающий ПРОБЛЕМУ НА КОМПЬЮТЕР:


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

1.) Скачать zipped version of the app from a file sharing site by clicking on this link.

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 и нажмите кнопку «Отправить». Это вызовет ошибку, показанную выше.

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

а) импортирующей в Maven проектов в вашей IDE, или

б) навигации в пределах распакованы каталогов и открытия с помощью текстового редактора..


Вы можете прочитать все журналы заголовков HTTP и Spring Boot на сайте обмена файлами by clicking on this link.

+0

Я обнаружил, что во время запроса/oauth/token в запросе отсутствует csrf cookie, поэтому запрос прерывается csrf-фильтром. Отсюда видно, что ошибки. – CrawlingKid

+0

@ Md.MinhajurRahman Большое спасибо. Я рассмотрю это сегодня. Что вы предлагаете мне делать с информацией, которую вы делитесь? – CodeMed

+0

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

ответ

-3

Ваш 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); 
} 

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

+0

Этот ответ начал меня с подхода, который, мы надеемся, приведет к серверному решению. Я награждаю эту награду, хотя у нее есть проблемы. Например, значение 1, которое вы указываете для параметра области в параметре 'attr.getAttribute (« _ csrf », 1)», создает значение «null» для 'csrf'. Истинный 'scope' должен быть статическим свойством, относящимся к текущему сеансу. И могут быть и другие проблемы. Но это началось в многообещающем направлении. Я сообщаю результаты с большим количеством результатов. – CodeMed

1

Одна мысль, что пришло мне в голову:

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

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

+0

'http.sessionManagement(). SessionFixation(). None()' не решила проблему. Хотя я открыт для повторного получения нового токена CSRF после входа в систему, я бы предпочел что-то, что агностически для интерфейса. Например, передняя часть приложения 'authserver' теперь находится в FreeMarker, а внешний интерфейс' ui' - в AngularJS. @ Подход DollarCoffee обещает сделать то, что вы предлагаете, но из бэкэнда таким образом, который не зависит от интерфейса. Я просто не знаю, как его реализовать, поэтому я изучаю. FYI, я помещаю 'http.sessionManagement(). SessionFixation(). None(). И()' как самую первую строку после 'http' – CodeMed

Смежные вопросы