Similar to Authentication token passed to ControllerAdvice is null when running through MockMvc, my MockMvc tests for a Spring Boot 1.5.16 application with Spring Data REST and Spring Security always have a null Authentication parameter whether I add the context manually or with #WithUserDetails.
Is this a bug in Spring Security's testing code, or am I messing up somewhere?
The #RepositoryRestController methods look like:
#PostMapping("/resources/{id}/attributes")
public #ResponseBody ResponseEntity<?> attributes(
#PathVariable("id") long resourceId,
#RequestParam(value = "file", required = true) MultipartFile file,
Authentication authentication ) throws IOException {
// 2.
Subject.setAuthentication(authentication);
try (InputStream input = file.getInputStream()) {
attributeHandler.read(resourceId, file.getName(), input);
}
return ResponseEntity.ok(success());
}
and my MockMvc tests look like:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
public class RestControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
#Test
#WithUserDetails("myAttributeManagerUsername")
public void attributes() throws Exception {
// 1.
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
mockMvc.perform(
MockMvcRequestBuilders.fileUpload(
"/api/resources/1/attributes"
).file(attributesFile())
// 3. .with(authentication(authentication)))
.andExpect(status().isOk());
}
}
Within the test method (at 1.) I have verified that authentication is present, but when the controller method is called (at 2.) the authentication is null, even if I manually set the context (at 3.) via .sessionAttr() or .with() (as shown). When running the application outside of the tests, the controller methods do get a proper authentication token (at 2.) with authenticated subjects.
Any ideas what's wrong in my tests?
Gee. This is not likely to be particularly helpful but...
As part of my (not shown) infrastructure, a filter incorrectly reset the authentication prior to the API which triggered this bug.
Sorry about the noise.
Related
I have following user resource, method createUser is secured to ADMIN role.
#RestController
#RequestMapping("/api")
public class UserResource {
#PostMapping("/users")
#Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity<User> createUser(#Valid #RequestBody UserDTO userDTO) throws URISyntaxException {
log.debug("REST request to save User : {}", userDTO);
// rest of code
}
}
And following spring boot test
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyappApp.class)
public class UserResourceIntTest {
// other dependencies
#Autowired
FilterChainProxy springSecurityFilterChain;
private MockMvc restUserMockMvc;
private User user;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
UserResource userResource = new UserResource(userRepository, userService, mailService);
this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setMessageConverters(jacksonMessageConverter)
.apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.build();
}
#Test
#Transactional
#WithMockUser(username="user", password = "user", authorities = {"ROLE_USER"})
public void createUser() throws Exception {
// Create the User
ManagedUserVM managedUserVM = new ManagedUserVM();
// set user properties
restUserMockMvc.perform(post("/api/users")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
.andExpect(status().isCreated());
}
}
I expect the test to fail because api is only allowed for ADMIN role while mock is using USER role, but test is passing.
Any help will be really appreciated.
Note: The JHipster version I'm using is 5.2.0. No guarantees that this will work for all versions.
If you are using a service (which you should), you can annotate the service method. Using #WithMockUser in the integration test should then just work without having to make any other changes. Here's an example. Note that I'm also using a service interface (pass the "serviceImpl" flag in JDL), but it will work in the service implementation as well.
/**
* Service Interface for managing Profile.
*/
public interface ProfileService {
/**
* Delete the "id" profile.
*
* #param id the id of the entity
*/
#Secured(AuthoritiesConstants.ADMIN)
void delete(Long id);
The corresponding rest controller method (auto-generated by JHipster):
/**
* DELETE /profiles/:id : delete the "id" profile.
*
* #param id the id of the profileDTO to delete
* #return the ResponseEntity with status 200 (OK)
*/
#DeleteMapping("/profiles/{id}")
#Timed
public ResponseEntity<Void> deleteProfile(#PathVariable Long id) {
log.debug("REST request to delete Profile : {}", id);
profileService.delete(id);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
}
JHipster uses MockMvcBuilders.standaloneSetup that get passed a controller instantiated manually (not with Spring and therefore not with AOP).
Therefore the PreAuthorize is not intercepted and security check is skipped.
You can therefore either #Autowire your controller and pass it to MockMvcBuilders.standaloneSetup which kind of defies the purpose of usesing standalone setup or simply use a WebApplicationContext: MockMvcBuilders.webAppContextSetup with and autowired WepAppContext.
Try removing #WithMockUser annotation and change the test method as below
ManagedUserVM managedUserVM = new ManagedUserVM();
managedUserVM.setLogin(DEFAULT_LOGIN);
managedUserVM.setPassword(DEFAULT_PASSWORD);
managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
For complete test. You can refer to this.
Test Class
So I'm writing this web app with Spring Boot using Spring Data with JPA and Spring MVC and I would like to make mock controller tests. I figured out how to test the get method, but in my controllers post method a new JPA entity is being either persisted or updated with my service. Here is what my controller looks like:
#Controller
#RequestMapping("/registerMember")
public class RegisterMemberController {
#Autowired
private MemberService memberService;
#GetMapping
public String index(RegisterMemberBean registerMemberBean) {
return "registerMember";
}
#PostMapping
public String handleSubmit(#Valid RegisterMemberBean registerMemberBean, BindingResult bindingResult, Model model) {
Member member = registerMemberBean.getMember();
boolean isRepeatPasswordCorrect = !isRepeatPasswordIncorrect(member.getPassword(), registerMemberBean.getComparePassword());
if(isAnyErrors(isRepeatPasswordCorrect, !bindingResult.hasErrors())) {
if(!isRepeatPasswordCorrect) {
model.addAttribute("isRepeatPasswordIncorrect", true).
addAttribute("isRepeatPasswordIncorrectMsg", "Passwords don't match");
}
return "registerMember";
}
boolean errUsername = !memberService.isNoOtherEntityWithUserName(0, member.getUserName());
boolean errEmail = !memberService.isNoOtherEntityWithEmail(0, member.getEmail());
if(errUsername || errEmail) {
if(errUsername) {
model.addAttribute("isExistingUserName", true).addAttribute("isExistingUserNameMsg", "Already a user with that username");
} if(errEmail) {
model.addAttribute("isExistingEmail", true).addAttribute("isExistingEmailMsg", "Already a user with that email");
}
return "registerMember";
}
getMainService().save(member);
return redirectTo("index", new RedirectEntity("member", member.getId()));
}
}
Now in my mock controller test i want to make make sure that my post method does the following:
Reload the page if the BindingResults has any errors
My service persists the member JPA entity in db (if no errors)
Method redirects me to the index page
This is what my (poor) test class looks like so far:
#RunWith(SpringRunner.class)
#TestPropertySource(locations="classpath:application_test.properties")
#WebAppConfiguration
public class RegisterMemberControllerTest {
private MockMvc mockMvc;
#MockBean
private MemberService memberService;
#MockBean
private RegisterMemberController controller;
#Before
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).setViewResolvers(new StandaloneMvcTestViewResolver()).build();
controller.setMainService(memberService);
}
#Test
public void testIndex() throws Exception {
mockMvc.perform(get("/registerMember"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("registerMember");
}
#Test
public void testHandleSubmit() throws Exception {
RegisterMemberBean registerMemberBean = new RegisterMemberBean();
registerMemberBean.setMember(TestFixture.getValidMemberWithoutReferences());
Member member = TestFixture.getValidMember();
mockMvc.perform(post(Page.REGISTER_MEMBER)).andExpect(status().isOk());
when(mockMvc.perform(post(Page.REGISTER_MEMBER)).andExpect((ResultMatcher) memberService.save(member)).andExpect(forwardedUrl("redirect:/index/member=" + member.getId() + ".html")));
}
}
to my understanding spring boot uses Mockito. I have some experience with EasyMock but I would like to use the spring defaults as much as possible. Can someone show how to achieve this?
I think there is a little bit of confusion on what should and shouldn't be mocked.
If I read your question correctly, you are actually trying to Unit Test your RegisterMemberController. Therefore, you most likely should NOT make a mock of that class, but actually test that class.
I believe that you would be creating fakes/dummies/stubs/mocks/spies of your MemberService, RegisterMemberBean, and BindingResult classes.
It would be these classes that would be created by your unit test and handed to your controller during the test that will force the testing of the logic that you are interested in proving/disproving.
FYI, when verifying that the MemberService class was called, that is where you would use a mock. The rest of the classes could either be dummies or stubs.
Side Note: I would recommend removing the Model parameter from your handleSubmit() method since it doesn't seem to be used anywhere.
I need to run a JUnit vs Spring MVC test case in which preconditions include that certain data is present in the HTTP Session. Most important is that I cannot wire a session-scoped bean: I must access httpServletContext.getSession().
Before showing code, let me explain. The controller I need to test assumes that a certain datum is stored in session, otherwise throws an exception. And that is the correct behaviour for now, because that controller is never invoked without a session and the session is always initialized with application data at login time. And obviously the controller is under security.
In my test, I just need to test whether this controller returns either a redirection or a 404 not found according to the request parameters.
I thought building my test case such as
#Autowired
private HttpServletRequest httpServletRequest;
#Autowired
private ModuleManager moduleManager;
#Autowired
private WebApplicationContext webApplicationContext;
private MenuItem rootMenu;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception
{
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
// No asserzioni
.build();
rootMenu = moduleManager.getRootMenu()
.clone();
httpServletRequest.getSession()
.setAttribute(MenuItem.SESSION_KEY, rootMenu);
assertNotNull(rootMenu.getDescendant(existingSelectedMenu));
assertNull(rootMenu.getDescendant(notExistingMenu));
}
#Test
public void testNavigate() throws Exception
{
mockMvc.perform(get("/common/navigate?target=" + existingSelectedMenu))
.andExpect(status().is3xxRedirection());
assertNotSelected(rootMenu, existingSelectedMenu);
mockMvc.perform(get("/common/navigate?target=" + notExistingMenu))
.andExpect(status().is4xxClientError());
}
Part of the code is truly self-explaining. Anyway I expect /common/navigate to use the value I stored in the session. Like this
#RequestMapping(value = "/common/navigate",
method = RequestMethod.GET)
public String navigate(#RequestParam("target") String target) throws NotFoundException
{
MenuItem rootMenu = (MenuItem) httpServletRequest.getSession()
.getAttribute(MenuItem.SESSION_KEY);
if (rootMenu == null)
throw new RuntimeException("Menu not found in session"); //Never happens
MenuItem menuItem = rootMenu.getAndSelect(target);
if (menuItem == null)
throw new NotFoundException(MenuItem.class, target); //Expected
return "redirect:" + menuItem.getUrl();
}
Now guess. What happens when I run my code?
RuntimeException is thrown in the line I commented as the menu object is not found in the session
Obviously the question is implicit now, but I will still write it: how do I inject data into the Session object so that controllers under test will have them available as precondition?
Found the solution by myself now.
The problem is that the session itself must be mocked too. Spring provides a MockHttpSession class that does the trick. It can be pre-populated with all pre-conditions, but must be passed to every MockMvc request so that the mock will wire the session to the (mocked) servlet context.
Following code initializes the session
mockHttpSession = new MockHttpSession(webApplicationContext.getServletContext());
mockHttpSession.setAttribute(MenuItem.SESSION_KEY, rootMenu);
Following performs the request with mocked session wired to it
mockMvc.perform(get("/common/navigate?target=" + existingSelectedMenu).session(mockHttpSession))
.andExpect(status().is3xxRedirection());
In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())
Right now, I'm learning about implementing REST API with a Spring Security Framework.
My question is, after success login with spring security, how can i send the request to server and make sure the server know that i am have been authorized (already login with success)?
I have a some experiment code to do testing
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { WebAppConfig.class, SecurityConfig.class })
public class TheTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy filterChainProxy;
protected MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders//
.webAppContextSetup(wac)//
.addFilter(filterChainProxy)//
.build()//
;
}
#Test
public void testDoingArequest() throws Exception {
// login here
HttpSession session = mockMvc.perform(//
//
post("/login-process")//
.param("username", "theusername")//
.param("password", "thepassword")//
)//
.andDo(print())//
.andExpect(status().isFound())//
.andReturn().getRequest().getSession()//
;
// login is success and now trying to call request
this.mockMvc.perform(//
get("/doingRequest")//
.session((MockHttpSession) session)// <-- where this part must added to?
)//
.andExpect(status().isOk())//
.andDo(print())//
;
}
}
-
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//
.antMatchers("/doingRequest").authenticated()//
.anyRequest().permitAll()//
.and()//
.csrf().disable()//
.formLogin()//
.loginPage("/")//
.loginProcessingUrl("/login-process")//
.defaultSuccessUrl("/");
}
-
#Controller
public class TheController {
#RequestMapping(value = "doingRequest", method = RequestMethod.GET)
#ResponseBody
public String doingSomething() {
return "Only authorized user can read this";
}
}
-
Above code is running well but i dont know how to implementing the "session" part in HTTP. I'm expecting something like put a token or something in header or url in real life application/implementation not in the testing environment. How the client get the token? How do we call the request (with token embedd) in client code.?
Are you looking for mocking a session object.If yes then you need to import the mock session object, and in the test class you can create and use the object.
import org.springframework.mock.web.MockHttpSession;
MockHttpSession session = new MockHttpSession();
session.setAttribute("variable", object);
The configuration you have will use the server side session to maintain the security context, and the link with the client is the standard servlet JSESSIONID cookie, so this has nothing to do with Spring Security. Whether you actually want a session or not will depend on the nature of your client. If there is no state maintained between the client and server, then each request from the client must be separately authenticated/authorized. This might be done using Basic authentication for example, or something like an OAuth2 access token depending on your requirements.