Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
901 views
in Technique[技术] by (71.8m points)

spring - Invalid XSRF token at /oauth/token

Complete code for a Spring OAuth2 implementation of Multi-Factor Authentication has been uploaded to a file sharing site at this link. Instructions are given below to recreate the current problem on any computer in only a few minutes.


**CURRENT PROBLEM:**
Most of the authentication algorithm works correctly. The program does not break until the very end of the control flow shown below. Specifically, an `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error is being thrown at the end of the **SECOND PASS** below. The app in the link above was developed by adding a custom `OAuth2RequestFactory`, `TwoFactorAuthenticationFilter` and `TwoFactorAuthenticationController` to the `authserver` app of this Spring Boot OAuth2 GitHub sample. **What specific changes need to be made to the code below in order to resolve this CSRF token error and enable 2-factor authentication?** My research leads me to suspect that the `CustomOAuth2RequestFactory` (API at this link) might be the place to configure a solution because it defines ways for managing `AuthorizationRequest`s and `TokenRequest`s. **This section of the official OAuth2 spec indicates that the `state` parameter of the request made to the authorization endpoint is the place where the `csrf` token is added.** Also, the code in the link uses the Authorization Code Grant Type described at this link to the official spec, which would mean that Step C in the flow does not update the `csrf` code, thus triggering the error in Step D. (You can view the entire flow including Step C and Step D in the official spec.)
**CONTROL FLOW SURROUNDING THE CURRENT ERROR:**
The current error is being thrown during the **SECOND PASS** through `TwoFactorAuthenticationFilter` in the flowchart below. Everything works as intended until the control flow gets into the **SECOND PASS**. The following flowchart illustrates the control flow of the two factor authentication process that is employed by the code in the downloadable app. image Specifically, the Firefox `HTTP` Headers for the sequence of `POST`s and `GET`s show that the same `XSRF` cookie is sent with every request in the sequence. The `XSRF` token values do not cause a problem until after the `POST /secure/two_factor_authentication`, which triggers server processing at the `/oauth/authorize` and `/oauth/token` endpoints, with `/oauth/token` throwing the `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error. To understand the relationship between the above control flow chart and the `/oauth/authorize` and `/oauth/token` endpoints, you can compare the above flowchart side by side with the chart for the single factor flow at the official spec in a separate browser window. The **SECOND PASS** above simply runs through the steps from the one-factor official spec a second time, but with greater permissions during the **SECOND PASS**.
**WHAT THE LOGS SAY:**
The HTTP Request and Response Headers indicate that: 1.) A POST to `9999/login` with the correct `username` and `password` submitted results in a redirect to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` followed by a `GET 9999/secure/two_factor_authenticated`. One XSRF token remains constant across these exchanges. 2.) A POST to `9999/secure/two_factor_authentication` with the correct pin code sends the same `XSRF` token, and gets successfully re-directed to `POST 9999/oauth/authorize` and makes it into `TwoFactorAuthenticationFilter.doFilterInternal()` and proceeds to `request 9999/oauth/token`, but `9999/oauth/token` rejects the request because the same old XSRF token does not match a new `XSRF` token value, which was apparently created during the **FIRST PASS**. One obvious difference between `1.)` and `2.)` is that the second `request 9999/oauth/authorize` in `2.)` does not contain the url parameters which are included in the first request to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` in `1.)`, and also defined in the official spec. But it is not clear if this is causing the problem. Also, it is not clear how to access the parameters to send a fully formed request from the `TwoFactorAuthenticationController.POST`. I did a SYSO of the `parameters` `Map` in the `HttpServletRequest` for the `POST 9999/secure/two_factor_authentication` controller method, and all it contains are the `pinVal` and `_csrf` variables. You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
**A FAILED APPROACH:**
I tried @RobWinch's approach to a similar problem in the Spring Security 3.2 environment, but the approach does not seem to apply to the context of Spring OAuth2. Specifically, when the following `XSRF` update code block is uncommented in the `TwoFactorAuthenticationFilter` code shown below, the downstream request headers do show a different/new `XSRF` token value, but the same error is thrown. if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){ CsrfToken token = (CsrfToken) request.getAttribute("_csrf"); response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken()); } **This indicates that the `XSRF` configuration needs to be updated in a way that `/oauth/authorize` and `/oauth/token` are able to talk with each other and with the client and resource apps to successfully manage the `XSRF` token values.** Perhaps the `CustomOAuth2RequestFactory` is what needs to be changed to accomplish this. But how?
**RELEVANT CODE:**
The code for `CustomOAuth2RequestFactory` is: 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 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); } } The code for `TwoFactorAuthenticationFilter` is: //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 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())) {

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

One idea that popped to my head:

If session fixation is activated, a new session is created after the user authenticated successfully (see SessionFixationProtectionStrategy). This will also of course create a new csrf token if you use the default HttpSessionCsrfTokenRepository. Since you're mentioning the XSRF-TOKEN header I assume you use some JavaScript frontend. I could imagine that the original csrf token that was used for the login is stored and reused afterwards - which would not work because this csrf token is not valid anymore.

You may try disabling session fixation (http.sessionManagement().sessionFixation().none() or <session-management session-fixation-protection="none"/>) or re-get the current CSRF token after login.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...