【发布时间】:2014-09-19 19:37:12
【问题描述】:
我通过几个教程学习了 Spring Security(和前端开发)。但是,我对 CSRF 令牌感到非常困惑,而且我显然做错了什么。
我的 Spring Security 是使用 java 配置的,当我禁用 CSRF(使用下一个 sn-p)时,表单提交没有问题。
http.csrf().disable();
我对@987654321@的理解是我需要完成的步骤是:
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/ 文件夹!现在已修复。
标签: spring spring-security csrf csrf-protection