spring - 为什么我的 spring security 配置似乎拒绝有效的 csrf token

标签 spring spring-security csrf csrf-protection

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

我的 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”字段作为临时步骤包含在表单上作为可见字段。我假设这没有填充,但在我发布表单之前 token 清晰可见,因此问题似乎是在发布数据时发生,而不是在生成数据时发生。

整个表单语法如下所示:

        <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 token 存储在 HttpSession 中,因此一旦 HttpSession 过期,您配置的 AccessDeniedHandler 将收到 InvalidCsrfTokenException。如果您使用默认的 AccessDeniedHandler,浏览器将收到 HTTP 403 并显示一个糟糕的错误消息。”

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

我正在使用以下 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 token 也在 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 的 token 嵌入到表单中。

错误指出发现了 CSRF token “null”,但打印的行显示请求属性中存在“_csrf”值。

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

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

 http.csrf().disable();

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

希望这可以让别人理解我做错了什么。

最佳答案

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

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

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

看起来如果没有“th:action”属性,Thymeleaf 就不知道“注入(inject)”csrf token 。

关于spring - 为什么我的 spring security 配置似乎拒绝有效的 csrf token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24998240/

相关文章:

java - Spring Boot 应用程序无法使用 SSH 启动

java - Spring 表达式语言 - 在注释中获取系统属性

spring-mvc - Spring Boot + 安全性 + MVC + LDAP AD + SSO

ruby-on-rails - 如何在rspec rails请求测试中丢失authenticity_token进行POST?

laravel - 如何在 Laravel5 中禁用所有路由的 CSRF 保护

java - CXF Servlet API方法列出所有服务bean?

java - Hibernate查询缓存问题

java - 登录表单用户凭据而不是 LDAP Spring Security 中的硬编码 UserDn 和密码

java - Spring-security:登录用户页面时得到http状态405

javascript - 如何以安全的方式读写 OData 调用? (比如不容易受到 CSRF 攻击?)