我正在使用 Playframework 及其内置 CSRF 过滤器和 Security.Authenticator 系统制作一个简单的身份验证系统,但我遇到了一个问题:
当用户填写登录名/密码并提交输入时,出现以下错误:
在会话中找不到 CSRF 令牌
我检查了我的表单,CSRF 令牌确实存在并且正确放置(在标签内)
这是我的routes
:
GET /login controllers.Authentication.login
POST /login controllers.Authentication.authenticate
And my Authentication.java
class :
@play.filters.csrf.AddCSRFToken
public static Result login() {
if (Session.getAccount() != null) {
return redirect(controllers.routes.Instances.list());
}
LoginForm loginForm = new LoginForm();
if (ctx().session().containsKey("email")) {
loginForm.setEmail(ctx().session().get("email"));
}
if (request().queryString().containsKey("disconnected")) {
flash("warning", Messages.get("secure.logout")); // TODO : Warning flash message
}
Form<LoginForm> form = Form.form(LoginForm.class).fill(loginForm);
return ok(views.html.login.render(form));
}
@play.filters.csrf.AddCSRFToken
@play.filters.csrf.RequireCSRFCheck
public static Result authenticate() {
Form<LoginForm> form = Form.form(LoginForm.class).bindFromRequest();
if (form.hasErrors()) {
return badRequest(views.html.login.render(form));
}
LoginForm login = form.get();
Account account = Account.findByEmail(login.getEmail());
if (account == null || !account.checkPassword(login.getPassword())) {
form.reject("email", "secure.invalid.login");
return badRequest(views.html.login.render(form));
}
String next = null;
if (ctx().session().containsKey("next")) {
next = ctx().session().get("next");
if ("".equals(next)) {
next = null;
}
}
session().clear();
session("email", login.getEmail());
if (next != null) {
return redirect(next);
} else {
return redirect(controllers.routes.Instances.list());
}
}
我在 application.conf 中设置的属性:
csrf.token.name="csrf"
csrf.sign.tokens=true
这是我的表单(以表明令牌位置正确):
<form action="@controllers.routes.Authentication.authenticate" method="post" class="form-vertical" role="form">
<fieldset>
@defining(form("email")) { element =>
<div class="form-group fade-in two@if(element.hasErrors){ has-error}">
<label class="control-label" for="[email protected] /cdn-cgi/l/email-protection">Email:</label>
<input type="email" name="@element.name" id="[email protected] /cdn-cgi/l/email-protection" class="form-control" value="@element.value" placeholder="Enter your email" required />
@element.errors.map { error => <span class="help-block">@play.i18n.Messages.get(error.message)</span>}
</div>}
@defining(form("password")) { element =>
<div class="form-group fade-in three@if(element.hasErrors){ has-error}">
<label class="control-label" for="[email protected] /cdn-cgi/l/email-protection">Password:</label>
<input type="password" name="@element.name" id="[email protected] /cdn-cgi/l/email-protection" class="form-control" value="@element.value" placeholder="Enter your password" required />
@element.errors.map { error => <span class="help-block">@play.i18n.Messages.get(error.message)</span>}
</div>}
<div class="form-group actions fade-in four">
<div class="text-right">
<input type="submit" name="save" value="Sign me in" class="btn btn-primary" />
@helper.CSRF.formField
</div>
</div>
</fieldset>
</form>
Update 1: 我把所有的都删掉了@addCSRFToken
and @requireCSRFToken
并根据 @rhj 的建议定义了一个全局 CSRF 令牌。现在我得到了这个错误:
在表单正文中发现无效令牌
但正如您在 HTML 表单中看到的那样,令牌放置在表单内部,应该可以被识别!
Update 2:最让我烦恼的是,这个问题只出现在登录页面,其他地方都没有!是否需要先启用一些会话?我想不会,但也许吧?!
Update 3:更奇怪的是,我刚刚发现如果我改变的值csrf.token.name
并重新加载页面,它就可以工作了。然后,如果我注销,打开一个新页面并尝试再次登录,则会失败并显示以下消息Invalid token found in form body
。如果我再次改变值csrf.token.name
再做一次,它就会再次起作用。
Update 4: 我缩小了问题范围。我还使用 play.mvc.Security.Authenticator,似乎令牌验证仅在我在redirect()
。如果我直接进入登录页面,则不会显示错误消息。
最后更新!:我终于找到了问题,它是在另一个我没有怀疑的类中,它被调用并清除了会话,使令牌变得无用!