Как вручную установить аутентифицированного пользователя в Spring Security/SpringMVC

После того, как новый пользователь отправит форму "Новая учетная запись", я хочу вручную зарегистрировать этого пользователя, чтобы он не мог войти на следующую страницу.

Страница входа в обычную форму, проходящую через перехватчик безопасности spring, работает нормально.

В контроллере new-account-form я создаю UserPasswordAuthenticationToken и вручную устанавливаю его в SecurityContext:

SecurityContextHolder.getContext().setAuthentication(authentication);

На той же странице я позже проверю, что пользователь вошел в систему с помощью:

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

Это возвращает полномочия, которые я установил ранее при аутентификации. Все хорошо.

Но когда этот же код вызывается на самой следующей странице, которую я загружаю, токен аутентификации - это просто UserAnonymous.

Я не понимаю, почему он не сохранил аутентификацию, установленную в предыдущем запросе. Любые мысли?

  • Может ли это сделать, если идентификатор сеанса не настроен правильно?
  • Есть ли что-то, что может как-то переписать мою аутентификацию?
  • Возможно, мне нужен еще один шаг, чтобы сохранить аутентификацию?
  • Или есть что-то, что мне нужно сделать, чтобы объявить аутентификацию на протяжении всего сеанса, а не на какой-либо запрос?

Просто найдите некоторые мысли, которые могут помочь мне увидеть, что происходит здесь.

Ответ 1

У меня была такая же проблема, как и вы. Я не помню подробностей, но следующий код заставил меня работать. Этот код используется в потоке Webflow Spring, следовательно, классы 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);
    }

}

Ответ 2

Я не мог найти никаких других полных решений, поэтому я думал, что я отправлю свои. Это может быть немного взломанным, но оно решило проблему для вышеупомянутой проблемы:

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

Ответ 3

В конечном счете выяснил корень проблемы.

Когда я создаю контекст безопасности вручную, объект сеанса не создается. Только когда обработка завершает обработку, механизм безопасности Spring реализует, что объект сеанса имеет значение null (когда он пытается сохранить контекст безопасности в сеансе после обработки запроса).

В конце запроса Spring Безопасность создает новый объект сеанса и идентификатор сеанса. Однако этот новый идентификатор сеанса никогда не попадает в браузер, поскольку он встречается в конце запроса после того, как был сделан ответ на браузер. Это приводит к потере нового идентификатора сеанса (и, следовательно, контекста безопасности, содержащего моего пользователя вручную), когда следующий запрос содержит предыдущий идентификатор сеанса.

Ответ 4

Включите ведение журнала отладки, чтобы получить лучшее представление о том, что происходит.

Вы можете узнать, установлены ли файлы cookie сеанса с помощью отладчика на стороне браузера, чтобы просмотреть заголовки, возвращаемые в ответах HTTP. (Есть и другие способы.)

Одна из возможностей заключается в том, что SpringSecurity устанавливает безопасные сеансовые куки, а ваша следующая страница имеет URL-адрес "http" вместо URL-адреса "https". (Браузер не отправит безопасный файл cookie для URL-адреса "http".)

Ответ 5

Новая функция фильтрации в Servlet 2.4 в основном устраняет ограничение, которое фильтры могут использовать только в потоке запросов до и после обработки фактического запроса сервером приложений. Вместо этого фильтры Servlet 2.4 теперь могут взаимодействовать с диспетчером запросов в каждой точке отправки. Это означает, что когда веб-ресурс перенаправляет запрос на другой ресурс (например, сервлет, перенаправляющий запрос на страницу JSP в том же приложении), фильтр может работать до того, как запрос будет обработан целевым ресурсом. Это также означает, что если веб-ресурс включает вывод или функцию из других веб-ресурсов (например, страницу 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();

Ответ 6

Я пытался протестировать приложение extjs, и после успешной установки testAuthenticationToken это внезапно прекратило работу без очевидной причины.

Я не мог заставить вышеупомянутые ответы работать, поэтому мое решение состояло в том, чтобы пропустить этот бит spring в тестовой среде. Я ввел шов вокруг spring следующим образом:

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

Пользователь - это настраиваемый тип.

Затем я обертываю его в класс, у которого есть только опция для тестового кода для переключения spring.

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 в классы, где это требуется.