W świecie wiosennym wiele się zmieniło od czasu odpowiedzi na to pytanie. Wiosna uprościła wprowadzanie bieżącego użytkownika do kontrolera. W przypadku innych ziaren Spring przyjął sugestie autora i uprościł zastrzyk „SecurityContextHolder”. Więcej szczegółów znajduje się w komentarzach.
To jest rozwiązanie, z którym skończyłem. Zamiast używać SecurityContextHolder
w moim kontrolerze, chcę wstrzyknąć coś, co używa SecurityContextHolder
pod maską, ale abstrahuje od mojego kodu klasę podobną do singletona. Nie znalazłem innego sposobu niż zrobienie tego z własnym interfejsem:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Teraz mój kontroler (lub cokolwiek POJO) wyglądałby tak:
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
}
}
A ponieważ interfejs jest punktem odsprzęgania, testowanie jednostkowe jest proste. W tym przykładzie używam 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();
}
}
Domyślna implementacja interfejsu wygląda następująco:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
I wreszcie produkcyjna konfiguracja wiosenna wygląda następująco:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Wydaje się bardziej niż trochę głupie, że Spring, pojemnik do wstrzykiwania zależności wszystkich rzeczy, nie dostarczył sposobu na wstrzyknięcie czegoś podobnego. Rozumiem, że SecurityContextHolder
został odziedziczony z acegi, ale nadal. Chodzi o to, że są tak blisko - gdyby tylko SecurityContextHolder
mieli getter, aby uzyskać bazową SecurityContextHolderStrategy
instancję (która jest interfejsem), moglibyście to wstrzyknąć. W rzeczywistości nawet otworzyłem w tym celu kwestię Jiry .
I ostatnia rzecz - zasadniczo zmieniłem odpowiedź, którą tu wcześniej miałem. Sprawdź historię, jeśli jesteś ciekawy, ale, jak wskazał mi współpracownik, moja poprzednia odpowiedź nie zadziałałaby w środowisku wielowątkowym. Bazą SecurityContextHolderStrategy
używaną przez SecurityContextHolder
jest domyślnie instancja ThreadLocalSecurityContextHolderStrategy
, która przechowuje SecurityContext
s w pliku ThreadLocal
. Dlatego niekoniecznie dobrym pomysłem jest wstrzyknięcie SecurityContext
bezpośrednio do komponentu bean w czasie inicjalizacji - może za ThreadLocal
każdym razem być konieczne pobranie go w środowisku wielowątkowym, aby pobrać właściwy.