Полный код для реализации аутентификации многофакторной пружины 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 parameters
Map
в 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.
Я обнаружил, что во время запроса/oauth/token в запросе отсутствует csrf cookie, поэтому запрос прерывается csrf-фильтром. Отсюда видно, что ошибки. – CrawlingKid
@ Md.MinhajurRahman Большое спасибо. Я рассмотрю это сегодня. Что вы предлагаете мне делать с информацией, которую вы делитесь? – CodeMed
Я потратил пару часов, чтобы выяснить точную причину, и попытался исправить это несколькими путями, но, наконец, я должен застрять на этом последнем этапе, когда я обнаружил, что случай, который я поделил с вами. Меня интересует решение. Пожалуйста, поделитесь им, если он будет установлен в любом случае. – CrawlingKid