简体   繁体   English

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

[英]How to manually set an authenticated user in Spring Security / SpringMVC

After a new user submits a 'New account' form, I want to manually log that user in so they don't have to login on the subsequent page.新用户提交“新帐户”表单后,我想手动登录该用户,这样他们就不必在后续页面上登录。

The normal form login page going through the spring security interceptor works just fine.通过 spring 安全拦截器的正常表单登录页面工作正常。

In the new-account-form controller I am creating a UsernamePasswordAuthenticationToken and setting it in the SecurityContext manually:在 new-account-form 控制器中,我正在创建一个 UsernamePasswordAuthenticationToken 并在 SecurityContext 中手动设置它:

SecurityContextHolder.getContext().setAuthentication(authentication);

On that same page I later check that the user is logged in with:在同一页面上,我稍后检查用户是否登录:

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

This returns the authorities I set earlier in the authentication.这将返回我之前在身份验证中设置的权限。 All is well.一切都很好。

But when this same code is called on the very next page I load, the authentication token is just UserAnonymous.但是当在我加载的下一页上调用相同的代码时,身份验证令牌只是 UserAnonymous。

I'm not clear why it did not keep the authentication I set on the previous request.我不清楚为什么它没有保留我在上一个请求中设置的身份验证。 Any thoughts?有什么想法吗?

  • Could it have to do with session ID's not being set up correctly?是否与会话 ID 未正确设置有关?
  • Is there something that is possibly overwriting my authentication somehow?是否有可能以某种方式覆盖我的身份验证?
  • Perhaps I just need another step to save the authentication?也许我只需要另一个步骤来保存身份验证?
  • Or is there something I need to do to declare the authentication across the whole session rather than a single request somehow?或者我需要做些什么来声明整个会话而不是单个请求的身份验证?

Just looking for some thoughts that might help me see what's happening here.只是寻找一些可能帮助我了解这里发生的事情的想法。

I had the same problem as you a while back. 我和你有一段时间有同样的问题。 I can't remember the details but the following code got things working for me. 我记不起细节,但下面的代码让我觉得有用。 This code is used within a Spring Webflow flow, hence the RequestContext and ExternalContext classes. 此代码在Spring Webflow流中使用,因此使用RequestContext和ExternalContext类。 But the part that is most relevant to you is the doAutoLogin method. 但与您最相关的部分是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);
    }

}

I couldn't find any other full solutions so I thought I would post mine. 我找不到任何其他完整的解决方案,所以我想我会发布我的。 This may be a bit of a hack, but it resolved the issue to the above problem: 这可能有点像黑客,但它解决了上述问题的问题:

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

Ultimately figured out the root of the problem. 最终找出了问题的根源。

When I create the security context manually no session object is created. 手动创建安全上下文时,不会创建任何会话对象。 Only when the request finishes processing does the Spring Security mechanism realize that the session object is null (when it tries to store the security context to the session after the request has been processed). 只有当请求完成处理时,Spring Security机制才会意识到会话对象为空(当它在处理请求后尝试将安全上下文存储到会话中时)。

At the end of the request Spring Security creates a new session object and session ID. 在请求结束时,Spring Security会创建一个新的会话对象和会话ID。 However this new session ID never makes it to the browser because it occurs at the end of the request, after the response to the browser has been made. 但是,这个新的会话ID永远不会进入浏览器,因为它发生在请求结束后,在对浏览器做出响应之后。 This causes the new session ID (and hence the Security context containing my manually logged on user) to be lost when the next request contains the previous session ID. 当下一个请求包含先前的会话ID时,这会导致新的会话ID(以及包含我的手动登录用户的安全上下文)丢失。

Turn on debug logging to get a better picture of what is going on. 打开调试日志记录以更好地了解正在发生的事情。

You can tell if the session cookies are being set by using a browser-side debugger to look at the headers returned in HTTP responses. 您可以使用浏览器端调试器来查看是否正在设置会话cookie,以查看HTTP响应中返回的标头。 (There are other ways too.) (还有其他方法。)

One possibility is that SpringSecurity is setting secure session cookies, and your next page requested has an "http" URL instead of an "https" URL. 一种可能性是SpringSecurity正在设置安全会话cookie,并且您请求的下一页有一个“http”URL而不是“https”URL。 (The browser won't send a secure cookie for an "http" URL.) (浏览器不会为“http”URL发送安全cookie。)

The new filtering feature in Servlet 2.4 basically alleviates the restriction that filters can only operate in the request flow before and after the actual request processing by the application server. Servlet 2.4中的新过滤功能基本上缓解了过滤器只能在应用服务器实际请求处理之前和之后的请求流中运行的限制。 Instead, Servlet 2.4 filters can now interact with the request dispatcher at every dispatch point. 相反,Servlet 2.4过滤器现在可以在每个调度点与请求调度程序进行交互。 This means that when a Web resource forwards a request to another resource (for instance, a servlet forwarding the request to a JSP page in the same application), a filter can be operating before the request is handled by the targeted resource. 这意味着当Web资源将请求转发给另一个资源(例如,将请求转发到同一应用程序中的JSP页面的servlet)时,过滤器可以在目标资源处理请求之前运行。 It also means that should a Web resource include the output or function from other Web resources (for instance, a JSP page including the output from multiple other JSP pages), Servlet 2.4 filters can work before and after each of the included resources. 它还意味着如果Web资源包含来自其他Web资源的输出或函数(例如,包含多个其他JSP页面的输出的JSP页面),Servlet 2.4过滤器可以在每个包含的资源之前和之后工作。 .

To turn on that feature you need: 要打开您需要的功能:

web.xml 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 这个RegistrationController

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

I was trying to test an extjs application and after sucessfully setting a testingAuthenticationToken this suddenly stopped working with no obvious cause. 我试图测试一个extjs应用程序,并在成功设置了testingAuthenticationToken后突然停止工作,没有明显的原因。

I couldn't get the above answers to work so my solution was to skip out this bit of spring in the test environment. 我无法得到上述工作的答案所以我的解决方案是在测试环境中跳过这一点弹簧。 I introduced a seam around spring like this: 我在这样的春天引入了一条缝:

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

User is a custom type here. 用户是此处的自定义类型。

I'm then wrapping it in a class which just has an option for the test code to switch spring out. 然后我将它包装在一个类中,该类只有一个选项可以让测试代码切换出来。

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

The test version just looks like this: 测试版本看起来像这样:

public class TestUserAccessor implements UserAccessor
{
    private static User _user;

    public TestUserAccessor(User user)
    {
        _user = user;
    }

    @Override
    public User getUser()
    {
        return _user;
    }
}

In the calling code I'm still using a proper user loaded from the database: 在调用代码中,我仍在使用从数据库加载的适当用户:

    User user = (User) _userService.loadUserByUsername(username);
    CurrentUserAccessor.UseTestingAccessor(user);

Obviously this wont be suitable if you actually need to use the security but I'm running with a no-security setup for the testing deployment. 显然,如果您确实需要使用安全性,但我正在运行测试部署的无安全设置,这将不合适。 I thought someone else might run into a similar situation. 我以为其他人可能会遇到类似的情况。 This is a pattern I've used for mocking out static dependencies before. 这是我之前用于模拟静态依赖项的模式。 The other alternative is you can maintain the staticness of the wrapper class but I prefer this one as the dependencies of the code are more explicit since you have to pass CurrentUserAccessor into classes where it is required. 另一种选择是你可以维护包装类的静态性,但我更喜欢这个,因为你必须将CurrentUserAccessor传递给需要它的类,因为代码的依赖性更加明确。

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(phone_number, password); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(phone_number, password); For me this statement throws BadCredentialsException.对我来说,这个语句会抛出 BadCredentialsException。 But credentials are correct.但凭据是正确的。 But still generated query shows that phone_number is compared with email id column from users table.但仍然生成的查询显示 phone_number 与用户表中的电子邮件 ID 列进行了比较。 How to deal with that?怎么处理? How can I code so that phone_number will compare with phone_number column from table?我如何编码以便 phone_number 与表中的 phone_number 列进行比较?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM