为啥我的 spring 安全配置似乎拒绝了有效的 csrf 令牌

Posted

技术标签:

【中文标题】为啥我的 spring 安全配置似乎拒绝了有效的 csrf 令牌【英文标题】:Why does my spring security configuration seem to reject a valid csrf token为什么我的 spring 安全配置似乎拒绝了有效的 csrf 令牌 【发布时间】:2014-09-19 19:37:12 【问题描述】:

我通过几个教程学习了 Spring Security(和前端开发)。但是,我对 CSRF 令牌感到非常困惑,而且我显然做错了什么。

我的 Spring Security 是使用 java 配置的,当我禁用 CSRF(使用下一个 sn-p)时,表单提交没有问题。

 http.csrf().disable();

我对@9​​87654321@的理解是我需要完成的步骤是:

    1) Use proper verbs 
    2) Enable csrf protection
    3) include _csrf as hidden fields in form.  

所有这些步骤听起来都很简单,但似乎对我不起作用,我收到错误:

HTTP Status 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

当我尝试提交我的注册表单时。

我做了更多检查,并在中间步骤中将“_csrf”字段作为可见字段包含在表单中。我假设这没有填充,但是在我发布表单之前令牌清晰可见,所以问题似乎是在发布数据时发生的,而不是在生成数据时发生的。

整个表单语法如下所示:

        <form method="POST" th:object="$individualRegistrationInfo">
            <table>
                <tr>
                    <td>Name:</td>
                    <td><input type="text" th:field="*name" /></td>
                    <td th:if="$#fields.hasErrors('name')"><p th:errors="*name">Incorrect Name</p></td>
                </tr>

                <tr>
                    <td>Username:</td>
                    <td><input type="text" th:field="*username" /></td>
                    <td th:if="$#fields.hasErrors('username')"><p th:errors="*username">Incorrect Username</p></td>
                </tr>
                <tr>
                    <td>Password:</td>
                    <td><input type="password" th:field="*password" /></td>
                    <td th:if="$#fields.hasErrors('password')"><p th:errors="*password">Incorrect Password</p></td>
                </tr>
                <tr>
                    <td>Email:</td>
                    <td><input type="email" th:field="*email" /></td>
                    <td th:if="$#fields.hasErrors('email')"><p th:errors="*email">Incorrect Email</p></td>
                </tr>

                <tr>
                    <td>Confirm Email:</td>
                    <td><input type="email" th:field="*confirmEmail" /></td>
                    <td th:if="$#fields.hasErrors('confirmEmail')"><p th:errors="*confirmEmail">Incorrect Email Confirmation</p></td>
                </tr>  
                <tr>
                    <td>Region:</td>
                    <td><select th:field="*regionName">
                    <option value="NONE">----Select----</option>
                      <option th:each="region : $regions" th:value="$region" th:text="$region">RegionTemplate</option>    
                    </select></td>
                    <td th:if="$#fields.hasErrors('regionName')"><p th:errors="*regionName">Region Name</p></td>
                </tr> 
                <tr><td>
                <span th:text= "$_csrf.parameterName">CSRF Parm Name</span></td>
               <td> <span th:text= "$_csrf.token">CSRF Token value</span> </td></tr>
                <tr>
                    <td colspan="3">
                        <input type="submit" value="Register" />
                    </td>
                </tr>
                  <input type="hidden" name="$_csrf.parameterName" value="$_csrf.token"/>
            </table>
        </form>

上一篇文章提到了一个项目,具体来说: “一个问题是预期的 CSRF 令牌存储在 HttpSession 中,因此一旦 HttpSession 过期,您配置的 AccessDeniedHandler 将收到 InvalidCsrfTokenException。如果您使用默认的 AccessDeniedHandler,浏览器将收到 HTTP 403 并显示一个糟糕的错误消息。”

我根本没有控制 HttpSession,所以我不确定会话超时设置为(或如何覆盖它)。但是,由于我正在重建应用程序,然后直接测试,会话超时似乎不太可能。

我正在使用以下 gradle 依赖项:

compile group: 'org.springframework', name: 'spring-webmvc', version:'4.0.5.RELEASE'
compile group: 'org.springframework', name: 'spring-context-support', version:'4.0.5.RELEASE'

compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version:'1.1.4.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version:'1.1.4.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version:'1.1.4.RELEASE'
testCompile group: 'org.springframework.security', name:'spring-security-test', version:'4.0.0.M1'

所以,当我通过三个建议的步骤进行最终检查时:

    1) Use proper verbs (POST is clearly visible on the form code snippet)
    2) Enable csrf protection (http.csrf().disable(); is commented out and _CSRF shows in form)
    3) include _csrf as hidden fields in form. (clearly visible on the form code snippet) 

于是我到达了

    4) I am missing something ! 

谁能建议我可能缺少什么?

感谢您抽出宝贵时间阅读本文。

7 月 29 日中午更新

another guide 包含有关 CSRF 保护的其他信息。

特别是可以使用AccessDeniedHandler。

我现在已经实现了这个,并在这个例程中包含了一些额外的日志记录细节。 我目前观察到的是,提交前页面上显示的 _csrf 令牌也在 AccessDeniedHandler 中报告。

处理程序实现如下:

    static class CustomAccessDeniedHandler implements AccessDeniedHandler 
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
            throws IOException, ServletException 

        logger.warn("Arrived in custom access denied handler.");
        HttpSession session = request.getSession();
        System.out.println("Session is " +session );
        System.out.println("Session id = " + session.getId());
        System.out.println("Session max interval="+session.getMaxInactiveInterval());
        System.out.println("Session last used="+session.getLastAccessedTime());
        System.out.println("Time now="+new Date().getTime());

        System.out.println();
        System.out.println("csrf:");
        Object csrf = request.getAttribute("_csrf");

        if (csrf==null)  
            System.out.println("csrf is null");
         else  
            System.out.println(csrf.toString());
            if (csrf instanceof DefaultCsrfToken)  
                DefaultCsrfToken token = (DefaultCsrfToken) csrf;
                System.out.println("Parm name " + token.getParameterName());
                System.out.println("Token " + token.getToken());
            

        
        System.out.println();
        System.out.println("Request:");
        System.out.println(request.toString());
        System.out.println();
        System.out.println("Response:");
        System.out.println(response.toString());
        System.out.println();
        System.out.println("Exception:");
        System.out.println(accessDeniedException.toString());
    

这个附加日志的输出是:

Session is org.apache.catalina.session.StandardSessionFacade@579ebbb8
Session id = 7CC03DAFF6BC34E28F5E91974C7E4BA5
Session max interval=1800
Session last used=1406630832436
Time now=1406630878254

csrf:
org.springframework.security.web.csrf.DefaultCsrfToken@763659f8
Parm name _csrf
Token 1e9cb3cf-c111-4b05-aace-4f8480b7d67b

Request:
   org.springframework.security.web.context.HttpSessionSecurityContextRepository$Servlet3SaveToSessionRequestWrapper@6a4ce569

Response:
    org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper@5e698704

Exception:
org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

在这种特殊情况下,1e9cb3cf-c111-4b05-aace-4f8480b7d67b 的令牌被嵌入到表单中。

错误表明找到了 CSRF 令牌“null”,但打印行显示请求属性中存在“_csrf”值。

7 月 30 日更新 我已将此问题复制到一个较小的示例项目中,该项目现在可在 GitHub 上找到: https://github.com/Mark-Allen/csrf-example.git

在其初始状态,应用程序接受一个名称,然后重新显示该名称。 当 SecurityWebConfiguration 的第 52 行被注释掉(需要注释的代码见下文)然后应用程序失败。

 http.csrf().disable();

根据@Bart 的建议,我添加了几个调试语句来显示关键阶段的会话 ID。

希望这可以使某人了解我做错了什么。

【问题讨论】:

会话 ID 是否保留在后续请求中? @Bart 我没有明确使用会话 ID,除非在我刚刚更新到这个问题的日志中。我不知道如何回答这个问题。 @Bart - 好的,将会话拉入各种方法非常简单,并且会话 ID 在整个应用程序中似乎一致(未启用 csrf) 启用 csrf 时应用程序无法运行。谢谢你的建议! 我去看了你的 GitHub 示例,但它不包含任何代码。 糟糕——我没有在 git 存储库中包含 src/ 文件夹!现在已修复。 【参考方案1】:

我不知道您是否找到了答案(或转移到电梯的其他部分),但我遇到了类似的问题并找到了解决方案。

我正在运行类似的设置,看起来您也在使用 Thymeleaf。

当我阅读有关 Spring Security 集成 (http://www.thymeleaf.org/doc/springsecurity.html) 的 Thymeleaf 文档时,我注意到他们在表单标签中使用“th:action”而不是“action”。这解决了我的问题。

看起来没有“th:action”属性,Thymeleaf 不知道“注入”csrf 令牌。

【讨论】:

谢谢你的雷。我正在使用 Thymeleaf,我根本没有指定“动作”。我已经尝试过你的建议,但它仍然不适合我。很高兴它为你工作!感谢您的回复。 这里有同样的问题。那件事只花了我2小时才发现。找不到任何文档,提到 th:action 是强制性的。【参考方案2】:

太老了,但我终于找到了答案。 隐藏字段的名称需要是_csrf。

<input type="hidden" name="_csrf" value="$_csrf.token"/>

【讨论】:

以上是关于为啥我的 spring 安全配置似乎拒绝了有效的 csrf 令牌的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 GPU 拒绝接受共享内存配置而不发出错误?

春季安全 |为啥当它们有效时我会得到错误的凭据?

为啥类型系统拒绝我看似有效的程序?

为啥 web3.js 拒绝有效的 RSK 智能合约地址?

拒绝连接我的服务器错误,为啥?

为啥安装完tomcat 以后,每次启动都是拒绝访问,急需答案!