Spring MVC 如何在Spring Security / SpringMVC中手动设置已验证用户

6fe3ivhb  于 2023-01-31  发布在  Spring
关注(0)|答案(6)|浏览(102)

在新用户提交“新帐户”表单后,我希望手动登录该用户,这样他们就不必在后续页面上登录。
通过springsecurityinterceptor的普通表单登录页面工作正常。
在新帐户表单控制器中,我创建了一个UsernamePasswordAuthenticationToken,并在SecurityContext中手动设置它:

SecurityContextHolder.getContext().setAuthentication(authentication);

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

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

这将返回我先前在身份验证中设置的权限。
但是,当在我加载的下一个页面上调用相同的代码时,身份验证令牌只是UserAnonymous。
我不清楚为什么它没有保持我在上一次请求中设置的身份验证。有什么想法吗?

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

只是想找些能帮我搞清楚到底发生了什么的想法。

9bfwbjaz

9bfwbjaz1#

我找不到任何其他完整的解决方案,所以我想我会张贴我的。这可能是一个有点黑客,但它解决了上述问题:

@Autowired
    AuthenticationServiceImpl authenticationManager;

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

i34xakig2#

不久前我遇到了和你一样的问题。我记不清细节了,但是下面的代码让我感觉很好。这段代码用在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);
    }

}
kkbh8khc

kkbh8khc3#

最终找到了问题的根源。
当我手动创建安全上下文时,并没有创建会话对象,只有当请求处理完毕时,Spring Security机制才会意识到会话对象为空(当请求处理完毕后,它试图将安全上下文存储到会话中时)。
在请求的末尾,Spring Security创建一个新的会话对象和会话ID。但是,这个新的会话ID不会出现在浏览器中,因为它出现在请求的末尾,在对浏览器做出响应之后。这导致当下一个请求包含上一个会话ID时,新的会话ID(以及包含手动登录用户的安全上下文)丢失。

eqzww0vc

eqzww0vc4#

打开调试日志记录以更好地了解正在发生的事情。
您可以使用浏览器端调试器查看HTTP响应中返回的报头,从而判断会话cookie是否被设置(也有其他方法)。
一种可能性是SpringSecurity设置了安全会话cookie,而您请求的下一个页面的URL是“http”而不是“https”(浏览器不会为“http”URL发送安全cookie)。

vvppvyoh

vvppvyoh5#

Servlet 2.4中的新过滤特性基本上消除了过滤器只能在应用服务器实际处理请求之前和之后在请求流中操作的限制,取而代之的是,Servlet 2.4过滤器现在可以在每个调度点与请求调度器交互。(例如,servlet将请求转发到同一应用程序中的JSP页),则过滤器可以在目标资源处理请求之前运行。这还意味着,Web资源是否应该包括来自其他Web资源的输出或函数(例如,一个JSP页包含多个其他JSP页的输出),Servlet 2.4过滤器可以在每个包含的资源之前和之后工作。
要打开该功能,您需要:

网页.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();
vvppvyoh

vvppvyoh6#

我试图测试一个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();
    }
}

用户是此处的自定义类型。
然后我把它 Package 在一个类中,这个类只有一个选项,可以让测试代码切换到弹出状态。

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

显然,如果你真的需要使用安全性,这是不合适的,但是我在测试部署中运行了一个没有安全性的设置。我想其他人可能会遇到类似的情况。这是我以前用来模拟静态依赖关系的模式。另一种方法是保持 Package 类的静态性,但我更喜欢这种方法,因为代码的依赖关系更加明确,因为必须将CurrentUserAccessor传递到需要它的类中。

相关问题