I'm using Spring 3.1.0.RELEASE with Spring Security 3.1. I want to inject my Spring user (i.e. the user who is currently logged in) into a controller. I want to do this as opposed to using
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
because it allows me to test the controller more easily with JUnit. However, I'm having a problem with my current setup. My question is, what is the correct way to inject my user (per request) into my controller? In my application context file, I have ...
<bean id="userDetails" class="com.myco.eventmaven.security.SecurityHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy />
</bean>
where I define my factory class as ...
public class SecurityHolder {
#Autowired
private static UserService userService;
public static MyUserDetails getUserDetails() {
final Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
final MyUserDetails reg = (MyUserDetails) a.getPrincipal();
final int userId = reg.getId();
final MyUserDetails foundUser = userService.findUserById(userId);
return foundUser;
} // if
} // getUserDetails
}
but the factory class repeatedly dies because "userService" fails to get autowired (the value is always null). I'm looking for a better way to do all this that can easily also integrate into my JUnit test. Any ideas?
Edit: Here's the JUnit test I'm looking to work with ...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/test/resources/testApplicationContext.xml" })
public class UserEventFeedsControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
...
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() {
...
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
...
#Test
public void testSubmitUserEventFeedsForm() throws Exception {
request.setRequestURI("/eventfeeds.jsp");
request.setMethod("POST");
final List<EventFeed> allEventFeeds = getAllEventFeeds();
request.setParameter("userEventFeeds", allEventFeeds.get(0).getId().toString());
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "user/eventfeeds");
}
You cannot autowire static fields. There are some workarounds, but I don't want to show them to you...
There are plenty of ways to access current user in an easier and more elegant matter:
Inject Principal to your controller (see When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?):
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
UserDetails ud = ((Authentication)principal).getPrincipal()
Develop your custom facade over SecurityContext
Replace built-in contextHolderStrategy in SecurityContextHolder for the purpose of testing
See also
How to get active user's UserDetails
Spring 3 MVC Controller integration test - inject Principal into method
Related
I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}
Suppose I develop a ticket order web service. There are some steps to order a ticket and need to keep some users data between the steps.
Suppose I use Spring (Boot) technology stack and MVC
How is better to implement it?
Use stateless REST and move the date back and forth from step to step using cookies?
Store it in session context?
Use stateful beans (what are they like in Spring? Prototype? )
Use some stateful protocol, like SOAP (is it stateful?)
It depends.
1 If you want to use multiple instances of your web service (for balance load, etc) then your choice is a stateless REST and token-based authentication
2 If you don't need this functionality you can store your session information in MVC Model (It will put it in session, anyway)
#RestController
#SessionAttributes("armUserSession")
public class SessionController {
#Autowired
private LoginService loginService;
#ModelAttribute("armUserSession")
public ArmUserSession getArmUserSession() {
return new ArmUserSession();
}
#CrossOrigin
#RequestMapping({"/login"})
public ArmUserSession login(#ModelAttribute("armUserSession") ArmUserSession userSession,
Model model,
#RequestParam(required = false) String login,
#RequestParam(required = false) String password) {
if (!userSession.isLoggedIn()) {
userSession = loginService.login(login, password);
model.addAttribute("armUserSession", userSession);
}
return userSession;
}
#CrossOrigin
#RequestMapping({"/logout"})
public ArmUserSession logout(SessionStatus status) {
status.setComplete();
return new ArmUserSession();
}
}
3 You can use session scoped beans too, but it is a little more complicated.
By default Spring beans are singletons. When you want to use session scoped bean (they are not singletons) in singleton your need a proxy.
#Service
public class LoginServiceImpl implements LoginService {
#Autowired
private ArmUserSessionProxy armUserSessionProxy;
#Override
public ArmUserSession login(String login, String password) {
ArmUserSession armUserSession = armUserSessionProxy.getArmUserSession();
...................................
}
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ArmUserSessionProxy {
private ArmUserSession armUserSession = new ArmUserSession();
public ArmUserSession getArmUserSession() {
return armUserSession;
}
}
I have some app with JWT authentication. And currently, I have such controller:
#RestController
#RequestMapping("users")
public class UserController {
#PostMapping(value = "{userId}/rate/inc")
public Double incRate(#PathVariable Long userId) {
return service.incUserRate(userId);
}
}
But, I want to get user by the token in the filter and pass it as a method's param. For example:
#PostMapping(value = "/rate/inc")
public Double incRate(User user) {
returnservice.incUserRate(user);
}
Is this possible?
Implement argument resolver and inject into your controller everything you need.
By default Spring allowes you to inject Principal object that by default contains users email (it is default realization in Spring Security). But you can implement injection of your business login users account by implementing Interface HandlerMethodArgumentResolver<User>.
I advice you to create an annotation like #AuthorizedUser in make mark your User param with this annotation. And according to this annotation presence in controller method, inject your user via HandlerMethodArgumentResolver.
#Component
public class UserArgumentHandlerResovler implements HandlerMethodArgumentResolver {
#Autowired
private UserRepository userRepository;
public boolean supportsParameter(MethodParameter parameter) {
return parameter.isAnnotationPresent(AuthorizedUser.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = (String) auth.getPrincipal(); // <- it is a pseudocode, check your Authentication implementation to get email for example.
return userRepository.findByEmail(email);
}
}
If you use Spring Security, you can resolve the current user and then have it provided to your controller method. However – if I'm not mistaken – you must declare it as Principal:
#PostMapping(value = "/rate/inc")
public Double incRate(Principal principal) {
returnservice.incUserRate((User)principal);
}
A more extensive example can be found at Baeldung.
I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using #WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.
Class
#Override
public ResponseEntity<EmployeeDTO> meGet() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
logger.debug("Endpoint called: me({})", principal);
EmployeeDTO result;
// Get user email from security context
String email = principal.getName(); // NPE here
// ...
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"eureka.client.enabled:false"})
#WithMockUser
#ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private SecurityContext securityContext;
#MockBean
private Authentication authentication;
#MockBean
private EmployeeRepository employeeRepository;
#BeforeClass
public static void setUp() {
}
#Before
#Override
public void resetMocks() {
reset(employeeRepository);
}
#Test
public void meGet() throws Exception {
when(securityContext.getAuthentication()).thenReturn(authentication);
securityContext.setAuthentication(authentication);
when(authentication.getPrincipal()).thenReturn(mockEmployee());
SecurityContextHolder.setContext(securityContext);
when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());
ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}
If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:
org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the #ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.
I could use PowerMock but I would like to avoid it if possible.
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it.
You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the #WithMockUser annotation was working properly.
What's key for this to work is using these snippets at class level:
#WebMvcTest(MeController.class)
#Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using #WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with #WithMockUser or mocking the service that handles such a call.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}
As part of Spring 3 MVC it is possible to inject the currently logged in user (Principle) object into a controller method.
E.g.
#Controller
public class MyController {
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(ModelMap model, Principal principal) {
String name = principal.getName();
... the rest here
}
}
This is documented as part of the Spring 3 documentation here:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments.
This works in the production code. However I don't know how to test this.
When I create an integration test (having set up spring security context as well)
and call the controller handle method then the Principal is always null!
public class FareTypeControllerIntegrationTest extends SpringTestBase {
#Autowired
private MyController controller;
#Autowired
private AnnotationMethodHandlerAdapter handlerAdapter;
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
#Test
public void testUpdate() throws Exception {
request.setRequestURI("/update");
request.setMethod(HttpMethod.POST.name());
... setup rest of request
ModelAndView mav = handlerAdapter.handle(request, response, controller);
.. rest of assertions
}
The tests are running correctly and everything except the Principal is null.
Any ideas?
TIA
Ayub
After a quick look into Spring sources this should work:
request.setUserPrincipal(somePrincipal);
I've tried to do this some time ago, here is the method i used to set up authentication.
protected void setSecurityContext(String login){
userDetailsTest = userManager.loadUserByUsername(login);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userDetailsTest, userDetailsTest.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication((Authentication) testingAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
Then i just call it in the #Before method of the test.
Hope it helps.
I do something like this in my tests prior to calling code using Spring Security (such as the Principal parameter resolver you are testing):
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("wiseau", "Love is blind"));