Как вручную установить аутентифицированного пользователя в 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 в классы, где это требуется.