问题
在新用户提交"新帐户"表单后,我想手动将该用户登录,这样他们就不必在后续页面上登录。
通过spring安全拦截器的普通表单登录页面工作得很好。
在新帐户表单控制器中,我正在创建UsernamePasswordAuthenticationToken并在SecurityContext中手动设置它:
SecurityContextHolder.getContext().setAuthentication(authentication);
在同一页面上,我稍后检查用户是否已登录:
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
这将返回我之前在身份验证中设置的权限。一切都很好。
但是当我加载的下一页上调用相同的代码时,身份验证令牌就是UserAnonymous。
我不清楚为什么它没有保留我在上一个请求中设置的身份验证。有什么想法吗?
- 它是否与会话ID未正确设置有关?
- 有什么东西可能会以某种方式覆盖我的身份验证吗?
- 也许我只需要另一步来保存身份验证?
- 或者我需要做些什么才能在整个会话中声明身份验证而不是单个请求?
只是寻找一些可能有助于我了解这里发生了什么的想法。
#1 热门回答(60 赞)
我和你有一段时间有同样的问题。我不记得细节,但下面的代码让我感觉很舒服。此代码在Spring Webflow流中使用,因此是RequestContext和ExternalContext类。但与你最相关的部分是doAutoLogin方法。
public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
RequestContext requestContext,
ExternalContext externalContext) {
try {
Locale userLocale = requestContext.getExternalContext().getLocale();
this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
return "success";
} catch (EmailAddressNotUniqueException e) {
MessageResolver messageResolvable
= new MessageBuilder().error()
.source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
.code("userRegistration.emailAddress.not.unique")
.build();
requestContext.getMessageContext().addMessage(messageResolvable);
return "error";
}
}
private void doAutoLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = this.authenticationProvider.authenticate(token);
logger.debug("Logging in with [{}]", authentication.getPrincipal());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
logger.error("Failure in autoLogin", e);
}
}
#2 热门回答(57 赞)
我找不到任何其他完整的解决方案,所以我想我会发布我的。这可能有点像黑客,但它解决了上述问题的问题:
public void login(HttpServletRequest request, String userName, String password)
{
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
// Create a new session and add the security context.
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
#3 热门回答(15 赞)
最终找出了问题的根源。
手动创建安全上下文时,不会创建任何会话对象。只有当请求完成处理时,Spring Security机制才会意识到会话对象为空(当它在处理请求后尝试将安全上下文存储到会话中时)。
在请求结束时,Spring Security会创建一个新的会话对象和会话ID。但是,这个新的会话ID永远不会进入浏览器,因为它发生在请求结束后,在对浏览器做出响应之后。当下一个请求包含先前的会话ID时,这会导致新的会话ID(以及包含我的手动登录用户的安全上下文)丢失。