【问题标题】:How to manually set an authenticated user in Spring Security / SpringMVC如何在 Spring Security / SpringMVC 中手动设置经过身份验证的用户
【发布时间】:2011-06-07 14:02:42
【问题描述】:

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

通过 spring 安全拦截器的普通表单登录页面工作正常。

在 new-account-form 控制器中,我正在创建一个 UsernamePasswordAuthenticationToken 并在 SecurityContext 中手动设置它:

SecurityContextHolder.getContext().setAuthentication(authentication);

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

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

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

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

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

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

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

【问题讨论】:

标签: java authentication spring-mvc spring-security


【解决方案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);
}

【讨论】:

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

不久前我和你有同样的问题。我不记得细节了,但是下面的代码对我有用。此代码在 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);
    }

}

【讨论】:

  • 谢谢,代码非常有助于帮助我知道我在正确的区域进行故障排除。看起来我有一把确凿的证据,它在手动身份验证后创建了一个新的会话 ID,但仍然从 cookie 中识别出旧的会话 ID。现在必须弄清楚为什么,但至少我显然走上了正轨。谢谢!
  • 任何遵循本指南的人也应该看到这个相关问题:stackoverflow.com/questions/4824395/…
  • 您能否解释一下您是如何获得 authenticationProvider 的
  • @s1moner3d 你应该可以通过 IoC 注入它 -> \@Autowired
  • @Configuration public class WebConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationProvider() throws Exception { return super.authenticationManagerBean(); } }
【解决方案3】:

终于找到了问题的根源。

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

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

【讨论】:

  • 老实说,这感觉更像是 Spring Security 的设计缺陷。有很多用其他语言编写的框架对此没有任何问题,但 Spring Security 就崩溃了。
  • 解决方案是?
  • 解决办法是什么?
【解决方案4】:

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

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

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

【讨论】:

  • 感谢这些都是非常有用且相关的建议!
【解决方案5】:

Servlet 2.4中新增的过滤特性,基本缓解了过滤器只能在应用服务器实际处理请求前后的请求流中操作的限制。相反,Servlet 2.4 过滤器现在可以在每个分派点与请求分派器交互。这意味着当 Web 资源将请求转发到另一个资源(例如,servlet 将请求转发到同一应用程序中的 JSP 页面)时,可以在目标资源处理请求之前运行过滤器。这也意味着如果一个 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>

注册控制器

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

【讨论】:

  • 很好的信息,但是将用户名和密码放入 url 是不好的。 1) 没有进行转义,因此带有特殊字符的用户名或密码很可能会被破坏,甚至更糟的是,被用作安全漏洞利用向量。 2) url 中的密码很糟糕,因为 url 经常被记录到磁盘上,这对安全性非常不利——你所有的明文密码都放在那里。
【解决方案6】:

我正在尝试测试一个 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 传递到需要它的类中。

【讨论】:

  • 如果您打算这样做,为什么不只是禁用测试环境中的安全性
猜你喜欢
  • 2013-04-23
  • 1970-01-01
  • 2020-10-20
  • 2015-08-09
  • 1970-01-01
  • 2014-01-12
  • 2014-10-03
  • 2019-07-18
  • 1970-01-01
相关资源
最近更新 更多