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

Dav*_*rks 102 java authentication spring-mvc spring-security

在新用户提交"新帐户"表单后,我想手动将该用户登录,这样他们就不必在后续页面上登录.

通过spring安全拦截器的普通表单登录页面工作得很好.

在新帐户形式的控制器中,我正在创建UsernamePasswordAuthenticationToken并手动在SecurityContext中设置它:

SecurityContextHolder.getContext().setAuthentication(authentication);
Run Code Online (Sandbox Code Playgroud)

在同一页面上,我稍后检查用户是否已登录:

SecurityContextHolder.getContext().getAuthentication().getAuthorities();
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 是否与会话ID未正确设置有关?
  • 有什么东西可能会以某种方式覆盖我的身份验证吗?
  • 也许我只需要另一步来保存身份验证?
  • 或者我需要做些什么才能在整个会话中声明身份验证而不是单个请求?

只是寻找一些可能有助于我了解这里发生了什么的想法.

Kev*_*dge 61

我和你有一段时间有同样的问题.我记不起细节,但下面的代码让我觉得有用.此代码在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);
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 你能解释一下你如何获得authenticationProvider (12认同)
  • 遵循此指南的任何人也应该看到相关问题:http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form (4认同)
  • 谢谢,代码非常有助于我知道我在正确的区域进行故障排除.看起来我有一个冒烟的枪,它在手动验证后创建一个新的会话ID,但仍然从cookie中识别旧的会话ID.要弄明白为什么现在,但至少我显然已走上正轨.谢谢! (2认同)

Stu*_*yre 61

我找不到任何其他完整的解决方案,所以我想我会发布我的.这可能有点像黑客,但它解决了上述问题的问题:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 你从哪里获得`authenticationManager`? (10认同)
  • +1 - 这对我有帮助!我错过了SPRING_SECURITY_CONTEXT更新.......但这有多"脏"? (7认同)
  • authenticationManager 在您的类中自动装配,就像这样@Autowired AuthenticationServiceImpl authenticationManager。而且你的xml配置中也必须有bean注入,这样Spring就知道要注入什么了。 (2认同)
  • 为什么有必要创建一个新会话?SecurityContext不处理吗? (2认同)
  • 我认为人们应该意识到通过 `setAuthentication()` 进行这种手动登录会绕过一些 spring 的东西,例如强制用户的最大并发会话限制,在登录时更改用户会话 ID 以防止会话固定,自动注册会话转换成`SessionRegistry`,也许更多。 (2认同)

Dav*_*rks 15

最终找出了问题的根源.

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

在请求结束时,Spring Security会创建一个新的会话对象和会话ID.但是,这个新的会话ID从未进入浏览器,因为它发生在请求结束后,在对浏览器做出响应之后.当下一个请求包含先前的会话ID时,这会导致新的会话ID(以及包含我的手动登录用户的安全上下文)丢失.

  • 老实说,这感觉就像春天安全中的设计缺陷一样.有很多用其他语言编写的框架对此没有任何问题,但Spring Security刚刚破解. (4认同)
  • 解决方案是? (3认同)
  • 什么是解决方案? (2认同)

Ste*_*n C 5

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

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

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


小智 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>
Run Code Online (Sandbox Code Playgroud)

这个RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail()
        + "&j_password=" + registrationModel.getPassword();
Run Code Online (Sandbox Code Playgroud)