Единственная проблема состоит в том, что даже после аутентификации с безопасностью Spring, боб пользователя/принципала не существует в контейнере, таким образом введение зависимости, это будет трудно. Прежде чем мы использовали безопасность Spring, мы создадим ограниченный по объему сессией боб, который имел текущий Принципал, введите это в "AuthService" и затем введите тот Сервис на большинство других служб в Приложении. Таким образом, те Сервисы просто назвали бы authService.getCurrentUser () для получения объекта. Если у Вас есть место в Вашем коде, где Вы получаете ссылку на тот же Принципал на сессии, можно просто установить его как свойство на ограниченном по объему сессией бобе.
Для последнего Spring приложение MVC я записал, я не вводил держателя SecurityContext, но у меня действительно был основной контроллер, что у меня было два служебных метода, связанные с этим... isAuthenticated () & getUsername (). Внутренне они делают вызов статического метода, который Вы описали.
, По крайней мере, тогда это находится только в однажды месте, если необходимо позже осуществить рефакторинг.
Вы могли использовать подход AOP Spring. Например, если у Вас есть некоторый сервис, который должен знать текущий принципал. Вы могли представить пользовательскую аннотацию т.е. @Principal, которые указывают, что этот Сервис должен быть основным зависимым.
public class SomeService {
private String principal;
@Principal
public setPrincipal(String principal){
this.principal=principal;
}
}
Тогда в Вашем совете, который я думаю потребности расширить MethodBeforeAdvice, проверка, что конкретный сервис имеет @Principal аннотацию и введи Основное имя, или установи его на 'АНОНИМНЫЙ' вместо этого.
Я соглашаюсь, что необходимость запросить SecurityContext для текущего пользователя воняет, это кажется очень неSpring способ решить эту проблему.
я записал статический класс "помощника" для контакта с этой проблемой; это грязно в этом, это - глобальный и статический метод, но я изобразил этот путь, если мы изменяем что-либо связанное с безопасностью, по крайней мере, я только должен изменить детали в одном месте:
/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
*
* @return User object for the currently logged in user, or null if no User
* is logged in.
*/
public static User getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();
// principal object is either null or represents anonymous user -
// neither of which our domain User object can represent - so return null
return null;
}
/**
* Utility method to determine if the current user is logged in /
* authenticated.
* <p>
* Equivalent of calling:
* <p>
* <code>getCurrentUser() != null</code>
*
* @return if user is logged in
*/
public static boolean isLoggedIn() {
return getCurrentUser() != null;
}
С тех пор, как был дан ответ на этот вопрос, в мире Spring многое изменилось. Spring упростил получение текущего пользователя в контроллере. Для других bean-компонентов Spring принял предложения автора и упростил внедрение SecurityContextHolder. Более подробная информация в комментариях.
Это решение, к которому я пришел. Вместо использования SecurityContextHolder
в моем контроллере я хочу внедрить что-то, что использует SecurityContextHolder
под капотом, но абстрагирует этот одноэлементный класс из моего кода. Я не нашел другого способа сделать это, кроме как развернуть свой собственный интерфейс, например:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Теперь мой контроллер (или какой-либо другой POJO) будет выглядеть так:
public class FooController {
private final SecurityContextFacade securityContextFacade;
public FooController(SecurityContextFacade securityContextFacade) {
this.securityContextFacade = securityContextFacade;
}
public void doSomething(){
SecurityContext context = securityContextFacade.getContext();
// do something w/ context
}
}
И поскольку интерфейс является точкой развязки , модульное тестирование очень просто. В этом примере я использую Mockito:
public class FooControllerTest {
private FooController controller;
private SecurityContextFacade mockSecurityContextFacade;
private SecurityContext mockSecurityContext;
@Before
public void setUp() throws Exception {
mockSecurityContextFacade = mock(SecurityContextFacade.class);
mockSecurityContext = mock(SecurityContext.class);
stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
controller = new FooController(mockSecurityContextFacade);
}
@Test
public void testDoSomething() {
controller.doSomething();
verify(mockSecurityContextFacade).getContext();
}
}
Реализация интерфейса по умолчанию выглядит так:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
И, наконец, производственная конфигурация Spring выглядит так:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Кажется более чем немного глупым, что Spring, зависимость контейнер для инъекций всего, не предоставил способ ввести что-то подобное. Насколько я понимаю, SecurityContextHolder
унаследован от acegi, но все же. Дело в том, что они так близки - если бы только SecurityContextHolder
имел геттер для получения базового экземпляра SecurityContextHolderStrategy
(который является интерфейсом), вы могли бы внедрить его. Фактически, я даже открыл проблему Jira по этому поводу.
И последнее - я только что существенно изменил ответ, который у меня был здесь раньше. Если вам интересно, проверьте историю, но, как мне указал коллега, мой предыдущий ответ не будет работать в многопоточной среде. Базовая SecurityContextHolderStrategy
, используемая SecurityContextHolder
, по умолчанию является экземпляром ThreadLocalSecurityContextHolderStrategy
, который сохраняет SecurityContext18
s . Следовательно, не обязательно вводить SecurityContext
непосредственно в компонент во время инициализации - его может потребоваться каждый раз извлекать из ThreadLocal
в многопоточном среды, поэтому будет получена правильная.
SecurityContextHolderStrategy
, используемая SecurityContextHolder
, по умолчанию является экземпляром ThreadLocalSecurityContextHolderStrategy
, который сохраняет SecurityContext18
s . Следовательно, не обязательно вводить SecurityContext
непосредственно в компонент во время инициализации - его может потребоваться каждый раз извлекать из ThreadLocal
в многопоточном среды, поэтому будет получена правильная. Базовая SecurityContextHolderStrategy
, используемая SecurityContextHolder
, по умолчанию является экземпляром ThreadLocalSecurityContextHolderStrategy
, который сохраняет SecurityContext18
s . Следовательно, не обязательно вводить SecurityContext
непосредственно в компонент во время инициализации - его может потребоваться каждый раз извлекать из ThreadLocal
в многопоточном среды, поэтому будет получена правильная. Да, статика в целом плохая, но в данном случае статика - это самый безопасный код, который можно написать. Поскольку контекст безопасности ассоциирует Принципала с текущим потоком, наиболее безопасный код будет обращаться к статическому из потока как можно более непосредственно. Скрытие доступа за классом обертки, который вводится, дает злоумышленнику больше точек для атаки. Им не нужен будет доступ к коду (который им будет сложно изменить, если банку подписать), им просто нужен способ переопределить конфигурацию, что можно сделать во время выполнения или просунуть немного XML в classpath. Даже использование вставки аннотаций в подписанный код было бы переопределено с помощью внешнего XML. Такой XML мог бы впрыскивать в работающую систему неавторизованный принцип. Вероятно, поэтому весна делает что-то настолько не-Spring, как в этом случае.
Я получаю аутентифицированного пользователя от HttpServletRequest.getUserPrincipal ();
Пример:
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;
import foo.Form;
@Controller
@RequestMapping(value="/welcome")
public class IndexController {
@RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model, HttpServletRequest request) {
if(request.getUserPrincipal() != null) {
String loginName = request.getUserPrincipal().getName();
System.out.println("loginName : " + loginName );
}
model.addAttribute("form", new Form());
return "welcome";
}
}