为啥我的 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();
我对@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/ 文件夹!现在已修复。 【参考方案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 令牌的主要内容,如果未能解决你的问题,请参考以下文章