首页 文章

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

提问于
浏览
96

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

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

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

SecurityContextHolder.getContext().setAuthentication(authentication);

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

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

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

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

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

  • 是否与会话ID未正确设置有关?

  • 有什么东西可能会以某种方式覆盖我的身份验证吗?

  • 也许我只需要另一步来保存身份验证?

  • 或者我需要做些什么才能在整个会话中声明身份验证而不是单个请求?

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

6 回答

  • 58

    最终找出了问题的根源 .

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

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

  • 60

    我试图测试一个extjs应用程序,在成功设置了testingAuthenticationToken后,突然停止工作,没有明显的原因 .

    我无法得到上述工作的答案所以我的解决方案是在测试环境中跳过这一点 spring . 我在这样的 Spring 天引入了一条缝:

    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传递给需要它的类,因为代码的依赖性更加明确 .

  • 5

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

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

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

  • 15

    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();
    
  • 5

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

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

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

相关问题