如何在Spring Security / SpringMVC中手动设置经过身份validation的用户

新用户提交“新帐户”表单后,我想手动将该用户login,以便他们不必在后续页面上login。

通过spring安全拦截器的正常forms的login页面工作得很好。

在新帐户表单控制器中,我将创build一个UsernamePasswordAuthenticationToken并手动将其设置在SecurityContext中:

SecurityContextHolder.getContext().setAuthentication(authentication); 

在同一页上,我稍后检查用户是否login:

 SecurityContextHolder.getContext().getAuthentication().getAuthorities(); 

这返回了我之前在身份validation中设置的权限。 一切都很好。

但是,当我加载的下一页上调用相同的代码时,身份validation令牌只是UserAnonymous。

我不清楚为什么它没有保持我以前的请求设置的身份validation。 有什么想法吗?

  • 会话ID的设置是否正确?
  • 有什么可能覆盖我的身份validation?
  • 也许我只需要另一个步骤来保存身份validation?
  • 还是有什么我需要做的是在整个会话中声明身份validation,而不是一个单一的请求?

只是寻找一些想法,可以帮助我看看这里发生了什么。

我也遇到了同样的问题。 我不记得的细节,但下面的代码得到的东西为我工作。 此代码在Spring Webflowstream程中使用,因此是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); } } 

我找不到任何其他完整的解决scheme,所以我想我会发布我的。 这可能是一个黑客,但它解决了上述问题的问题:

 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); } 

最终找出问题的根源。

当我手动创build安全上下文时,不会创build会话对象。 只有在请求完成处理后,Spring Security机制才会意识到会话对象是空的(当它试图在请求被处理后将安全上下文存储到会话中)。

在请求结束时,Spring Security创build一个新的会话对象和会话ID。 但是,这个新的会话ID从来不会传递给浏览器,因为它发生在请求的末尾,在对浏览器的响应之后。 这会导致新的会话ID(因此包含手动login的用户的安全上下文)在下一个请求包含以前的会话ID时丢失。

打开debugging日志logging以更好地了解正在发生的事情。

您可以通过使用浏览器端debugging器来查看是否正在设置会话Cookie,以查看HTTP响应中返回的标头。 (还有其他的方法。)

一种可能性是SpringSecurity正在设置安全会话cookie,并且您请求的下一个页面有一个“http”URL而不是“https”URL。 (浏览器不会为“http”URL发送安全的cookie。)

Servlet 2.4中新的过滤function基本上缓解了应用服务器实际处理请求之前和之后filter只能在请求stream中运行的限制。 相反,Servlet 2.4filter现在可以在每个调度点与请求分派器进行交互。 这意味着,当Web资源将请求转发给另一个资源(例如,将请求转发到同一个应用程序中的JSP页面的servlet)时,filter可以在请求被目标资源处理之前运行。 这也意味着,如果Web资源包含来自其他Web资源的输出或函数(例如,包含来自多个其他JSP页面的输出的JSP页面),那么Servlet 2.4filter可以在每个包含的资源之前和之后工作。 。

要打开该function,您需要:

web.xml中

  <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/<strike>*</strike></url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> 

这个RegistrationController

  return "forward:/login?j_username=" + registrationModel.getUserEmail() + "&j_password=" + registrationModel.getPassword(); 

我试图testing一个extjs应用程序,并成功设置testAuthenticationToken后突然停止工作,没有明显的原因。

我无法得到上述答案,所以我的解决scheme是在testing环境中跳过这一点。 我在这样的spring周围引入了一条缝线:

 public class SpringUserAccessor implements UserAccessor { @Override public User getUser() { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); return (User) authentication.getPrincipal(); } } 

用户在这里是一个自定义types。

然后,我将它包装在一个只有testing代码才能popup的类中。

 public class CurrentUserAccessor { private static UserAccessor _accessor; public CurrentUserAccessor() { _accessor = new SpringUserAccessor(); } public User getUser() { return _accessor.getUser(); } public static void UseTestingAccessor(User user) { _accessor = new TestUserAccessor(user); } } 

testing版本看起来像这样:

 public class TestUserAccessor implements UserAccessor { private static User _user; public TestUserAccessor(User user) { _user = user; } @Override public User getUser() { return _user; } } 

在调用代码中,我仍然使用从数据库加载的正确用户:

  User user = (User) _userService.loadUserByUsername(username); CurrentUserAccessor.UseTestingAccessor(user); 

显然这不适合,如果你真的需要使用安全性,但我正在运行testing部署没有安全的设置。 我以为别人可能会遇到类似的情况。 这是我以前用于模拟静态依赖关系的模式。 另一种select是你可以保持包装类的静态,但我更喜欢这一点,因为代码的依赖关系更加明确,因为你必须将CurrentUserAccessor传递到需要的地方。