/oauth/token处的XSRF令牌无效 [英] Invalid XSRF token at /oauth/token

查看:305
本文介绍了/oauth/token处的XSRF令牌无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

用于多重身份验证的Spring OAuth2实现的完整代码已上传到此链接的文件共享站点.下面给出了在几分钟之内在任何计算机上重现当前问题的说明.

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.


当前问题:

大多数身份验证算法都能正常工作.直到如下所示的控制流的最后,程序才会中断.具体来说,下面的 SECOND PASS 的末尾将引发Invalid CSRF token found for http://localhost:9999/uaa/oauth/token错误.通过将自定义OAuth2RequestFactoryTwoFactorAuthenticationFilterTwoFactorAuthenticationController添加到

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?

我的研究使我怀疑CustomOAuth2RequestFactory(

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 AuthorizationRequests and TokenRequests.

官方OAuth2规范的这一部分表明向授权端点发出的请求的state参数是添加csrf令牌的位置.

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.

此外,链接中的代码使用此链接中描述的授权代码授予类型到官方规范,这意味着流程中的步骤C不会更新csrf代码,因此会触发步骤D中的错误.(您可以在<中查看整个流程,包括步骤C和步骤D a href ="https://tools.ietf.org/html/rfc6749#section-4.1" rel ="nofollow noreferrer">官方规范.)

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.)


围绕当前错误的控制流:

在以下流程图的 SECOND PASS TwoFactorAuthenticationFilter期间抛出了当前错误.一切都会按预期进行,直到控制流进入第二次通过为止.

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.

具体来说,POSTGET序列的Firefox HTTP标头显示,序列中的每个请求都发送相同的XSRF cookie.直到POST /secure/two_factor_authentication之后,XSRF令牌值才引起问题,这会触发/oauth/authorize/oauth/token端点上的服务器处理,而/oauth/token会引发Invalid CSRF token found for http://localhost:9999/uaa/oauth/token错误.

Specifically, the Firefox HTTP Headers for the sequence of POSTs and GETs 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.

要了解上述控制流程图与/oauth/authorize/oauth/token端点之间的关系,可以并排比较上述流程图

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:

HTTP请求和响应标头指示:

The HTTP Request and Response Headers indicate that:

1.)使用正确的usernamepassword提交到9999/login的POST会导致重定向到9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v,然后是GET 9999/secure/two_factor_authenticated.在这些交换中,一个XSRF令牌保持不变.

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.)使用正确的PIN码到9999/secure/two_factor_authentication的POST发送相同的XSRF令牌,并成功将其重定向到POST 9999/oauth/authorize,并使其变为TwoFactorAuthenticationFilter.doFilterInternal(),然后进入request 9999/oauth/token,但是9999/oauth/token拒绝该请求,因为相同的旧XSRF令牌与新的XSRF令牌值不匹配,该值显然是在第一次通过期间创建的.

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.

1.)2.)之间的一个明显区别是2.)中的第二个request 9999/oauth/authorize不包含url参数,该参数包含在1.)中对9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v的第一个请求中,并且也已定义在官方规范中.但是尚不清楚这是否是导致问题的原因.

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.

此外,还不清楚如何访问参数以从TwoFactorAuthenticationController.POST发送完整格式的请求.我为POST 9999/secure/two_factor_authentication控制器方法在HttpServletRequest中对parameters Map进行了SYSO,它包含的所有内容都是pinVal_csrf变量.

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.

您可以在文件共享站点单击此链接来阅读所有HTTP标头和Spring Boot日志.

You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.


失败的方法:

我尝试了

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());
}

这表明XSRF配置需要以/oauth/authorize/oauth/token能够相互通信以及与客户端和资源应用程序进行通信以成功管理XSRF的方式进行更新令牌值.也许需要更改CustomOAuth2RequestFactory才能实现此目的.但是如何?

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?


相关代码:

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;
    }
}


在计算机上重新创建问题:

您可以按照以下简单步骤在几分钟内在任何计算机上重新创建问题:

You can recreate the problem on any computer in only a few minutes by following these simple steps:

1.)通过单击此链接从文件共享站点下载该应用程序的压缩版本.

1.) Download the zipped version of the app from a file sharing site by clicking on this link.

2.)键入以下内容以解压缩应用程序:tar -zxvf oauth2.tar(2).gz

2.) Unzip the app by typing: tar -zxvf oauth2.tar(2).gz

3.)导航到oauth2/authserver,然后键入mvn spring-boot:run,启动authserver应用.

3.) launch the authserver app by navigating to oauth2/authserver and then typing mvn spring-boot:run.

4.)导航到oauth2/resource,然后键入mvn spring-boot:run

4.) launch the resource app by navigating to oauth2/resource and then typing mvn spring-boot:run

5.)通过导航到oauth2/ui然后键入mvn spring-boot:run

5.) launch the ui app by navigating to oauth2/ui and then typing mvn spring-boot:run

6.)打开Web浏览器并导航到http : // localhost : 8080

6.) Open a web browser and navigate to http : // localhost : 8080

7.)单击Login,然后输入Frodo作为用户,并输入MyRing作为密码,然后单击以提交.

7.) Click Login and then enter Frodo as the user and MyRing as the password, and click to submit.

8.)输入5309作为Pin Code,然后单击提交". 这将触发上面显示的错误.

8.) Enter 5309 as the Pin Code and click submit. This will trigger the error shown above.

您可以通过以下方式查看完整的源代码:

You can view the complete source code by:

a.)将Maven项目导入到您的IDE中,或通过

a.) importing the maven projects into your IDE, or by

b.)在解压缩的目录中导航并使用文本编辑器打开.

b.) navigating within the unzipped directories and opening with a text editor.


您可以在文件共享站点单击此链接来阅读所有HTTP标头和Spring Boot日志.


You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.

推荐答案

一个突然出现的想法:

如果激活了会话固定,则在用户成功验证身份后将创建一个新会话(请参阅SessionFixationProtectionStrategy).如果您使用默认的HttpSessionCsrfTokenRepository,那么这当然也会创建一个新的csrf令牌.由于您提到的是XSRF-TOKEN标头,因此我假设您使用一些JavaScript前端.我可以想象原来用于登录的csrf令牌随后会被存储和重用-这将不起作用,因为此csrf令牌不再有效.

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.

您可以尝试禁用会话固定(http.sessionManagement().sessionFixation().none()<session-management session-fixation-protection="none"/>),或者在登录后重新获取当前的CSRF令牌.

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.

这篇关于/oauth/token处的XSRF令牌无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆