2011-01-12 134 views
89

新用户提交“新帐户”表单后,我想手动将该用户登录,以便他们不必在后续页面上登录。如何在Spring Security/SpringMVC中手动设置经过身份验证的用户

通过spring安全拦截器的正常形式的登录页面工作得很好。

在新的账户形式的控制器我创建一个UsernamePasswordAuthenticationToken和SecurityContext的人工设置:

SecurityContextHolder.getContext().setAuthentication(authentication); 

在同一页面后,我检查用户登录时:

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

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

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

我不清楚为什么它没有保留我在前一个请求中设置的身份验证。有什么想法吗?

  • 它可能与会话ID的设置不正确吗?
  • 有什么可能覆盖我的身份验证吗?
  • 也许我只是需要另一个步骤来保存身份验证?
  • 还是有什么我需要做的宣布整个会话的身份验证,而不是一个单一的请求?

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

+1

您可以按照我的答案http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form/7972971#7972971 – AlexK 2011-11-01 21:17:13

+1

读者,要小心的答案这个问题如果他们告诉你要做:`SecurityContextHolder.getContext()。setAuthentication(认证)`。它的工作原理很普遍,但是如果你这么做,会遇到严重的功能缺陷。有关更多信息,请参阅我的问题和答案:https://stackoverflow.com/questions/47233187/spring-security-manual-login-best-practice – goat 2017-11-28 02:47:36

回答

59

我和你有一样的问题。我不记得细节,但下面的代码为我工作。此代码在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

谢谢,该代码对帮助我知道我在正确的区域进行故障排除非常有帮助。看起来我有一支吸烟枪,它正在手动验证后创建一个新的会话ID,但旧的会话ID仍然从cookie中识别出来。要弄清楚为什么现在,但至少我明确地走上了正轨。谢谢! – 2011-01-13 00:47:54

+4

任何遵循本指南的人都应该看到这个相关问题:http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form – 2011-01-31 10:51:45

+12

你能解释一下,你是如何获得认证提供者 – Vivek 2015-05-08 05:28:21

5

打开调试日志记录以更好地了解正在发生的事情。

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

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

+0

谢谢这些都是非常有帮助和相关的建议! – 2011-01-13 00:48:27

15

最终找出问题的根源。

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

在请求结束时,Spring Security创建一个新的会话对象和会话ID。然而,这个新的会话ID从不会传递给浏览器,因为它发生在请求结束后,在对浏览器做出响应之后。当下一个请求包含以前的会话ID时,这会导致新的会话ID(因此包含我手动登录的用户的安全上下文)丢失。

5

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

要开启该功能,你需要:

的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(); 
54

我无法找到任何其他全方位的解决方案,所以我想我会发布我的。这可能是一个黑客位,但它解决了这个问题上面的问题:

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

我想测试一个ExtJS的应用和成功地后设置testingAuthenticationToken这个突然停了下来,没有明显原因的工作。

我无法获得上述答案,因此我的解决方案是在测试环境中跳过这一点。我在这样的弹簧周围引入了一条接缝:

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

用户在这里是自定义类型。

然后我把它包装在一个只有测试代码选项的类中才能弹出。

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

测试版看起来就像这样:

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

显然,这不会是如果您实际需要使用安全性,但适用于测试部署的无安全设置,则适用。我以为别人可能会遇到类似的情况。这是我以前用来模拟静态依赖关系的模式。另一种选择是你可以保持包装类的静态,但我更喜欢这个,因为代码的依赖关系更加明确,因为你必须将CurrentUserAccessor传递到需要的类中。

相关问题