/ oauth/token中的XSRF令牌无效

Cod*_*Med 14 spring spring-mvc spring-security spring-boot spring-oauth2

已将多重身份验证的Spring OAuth2实现的完整代码上载到此链接的文件共享站点.下面给出了说明,只需几分钟即可在任何计算机上重新创建当前问题.


当前问题:


大多数身份验证算法都能正常工作.程序不会中断,直到下面显示的控制流程结束.具体来说,Invalid CSRF token found for http://localhost:9999/uaa/oauth/token在下面的第二次通过结束时会抛出错误.在上面的链接的应用程序中加入一个定制开发OAuth2RequestFactory,TwoFactorAuthenticationFilterTwoFactorAuthenticationControllerauthserver应用此的春天启动了OAuth2 GitHub的样品. 需要对下面的代码进行哪些具体更改才能解决此CSRF令牌错误并启用双因素身份验证?

我的研究使我怀疑CustomOAuth2RequestFactory(此链接上的API)可能是配置解决方案的地方,因为它定义了管理AuthorizationRequests和TokenRequests的方法.

官方OAuth2规范的此部分指示state对授权端点发出的请求的参数csrf是添加令牌的位置.

此外,链接中的代码使用此链接中描述的授权代码授权类型到官方规范,这意味着流程中的步骤C不会更新csrf代码,从而在步骤D中触发错误.(您可以查看整个流程包括官方规范步骤C和步骤D. )


控制流量围绕当前错误:


正在所述过程中引发电流误差第二遍通过TwoFactorAuthenticationFilter在下面的流程图.一切都按预期工作,直到控制流进入第二通道.

以下流程图说明了可下载应用程序中的代码所采用的双因素身份验证过程的控制流程.

具体来说,s和s HTTP序列的Firefox 标题显示,序列中POST的每个请求都会发送GET相同的XSRFcookie.该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端点之间的关系,您可以在单独的浏览器窗口中将上述流程图与官方规范单因素流的图表并排比较.上面的第二次通过只是第二次执行单因素官方规范的步骤,但在第二次通过期间拥有更多权限.



日志说的是什么:


HTTP请求和响应标头指示:

1.)POST以9999/login正确的usernamepassword提交的结果重定向9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v后跟a GET 9999/secure/two_factor_authenticated.这些交换中,一个XSRF令牌保持不变.

2.)9999/secure/two_factor_authentication具有正确密码的POST 发送相同的XSRF令牌,并成功重定向POST 9999/oauth/authorize并进入TwoFactorAuthenticationFilter.doFilterInternal()并继续进行request 9999/oauth/token,但9999/oauth/token拒绝该请求,因为相同的旧XSRF令牌与新XSRF令牌值不匹配,显然是在FIRST PASS期间创建的.

之间一个明显的区别1.),并2.)是第二request 9999/oauth/authorize2.)不包含被包括在所述第一请求的url参数9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v1.),并且在还定义了正式规范.但目前尚不清楚这是否会导致问题.

此外,还不清楚如何访问参数以从中发送完全形成的请求TwoFactorAuthenticationController.POST.我做的一个SYSO parameters MapHttpServletRequestPOST 9999/secure/two_factor_authentication控制器的方法,并且它包含所有都是pinVal_csrf变量.

您可以通过单击此链接读取文件共享站点上的所有HTTP标头和Spring Boot日志.


失败的方法:


我尝试了@ RobWinch在Spring Security 3.2环境中解决类似问题的方法,但这种方法似乎并不适用于Spring OAuth2的上下文.具体来说,当在下面显示XSRFTwoFactorAuthenticationFilter代码中取消注释以下更新代码块时,下游请求标头会显示不同的/新XSRF标记值,但会抛出相同的错误.

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

这表明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);
    }
}
Run Code Online (Sandbox Code Playgroud)

代码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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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


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

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.)打开Web浏览器并导航到 http : // localhost : 8080

7.)单击Login,然后Frodo以用户MyRing身份和密码输入,然后单击以提交.

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

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

a.)将maven项目导入IDE,或者

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


您可以通过单击此链接读取文件共享站点上的所有HTTP标头和Spring Boot日志.

Dol*_*fee -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);
}
Run Code Online (Sandbox Code Playgroud)

我正在重新审视这个问题,因为我最初的答复草案被否决了。这个版本沿着同样的道路走得更远,我相信这是正确的方法。