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

时间:2014-07-28 15:06:24

标签: spring spring-security csrf csrf-protection

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

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

 http.csrf().disable();

我对here的理解是,我需要完成的步骤是:

    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。

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

2 个答案:

答案 0 :(得分:3)

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

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

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

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

答案 1 :(得分:0)

太老了,但最后我找到了答案。 隐藏字段的名称必须是_csrf。

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