Spring MockMVC, Spring security and Global Method Security during test - java

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

Related

Logout with JAAS login module

The question is a little bit longer than expected. Below is the link to a similar one (3rd post) where I didn't find the answer satisfying.
TL;DR
I am trying to logout using the JAAS Login Module. Here is the brief structure of the project:
LoginService is responsible for instantiating LoginContext when a user wants to log in:
#Service
public class LoginService {
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
The LoginController calls the method:
#RestController
#RequestMapping("/login")
public class LoginController {
private final LoginService loginService;
#Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
#PostMapping
public ResponseEntity<UserDTO> getUserDTOFrom(#Valid #RequestBody Credentials credentials) {
UserDTO userDTO = loginService.getUserDTOFrom(userForm);
// return response that depends on outcome in the login service
}
}
The issue arises when I want to logout previously logged in user. LoginContext is responsible for calling the logout method in the JAAS Login Module. For instance:
loginContext.logout();
The method in the JAAS Login Module:
public class JAASLoginModule implements LoginModule {
#Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
I don't have the LoginContext in LogoutService and unable to completely clear the previously authenticated subject.
I tried to create a singleton bean to get the same instance of the LoginContext:
#Configuration
public class LoginContextBean {
#Lazy
#Bean
public LoginContext getLoginContext(Credentials credentials) throws LoginException {
System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
return new LoginContext("Login", new JAASCallbackHandler(credentials));
}
}
#Service
public class LoginService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = loginContextProvider.getObject(credentials);
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
}
#Service
public class LogoutService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public void performLogout() {
LoginContext loginContext = loginContextProvider.getObject();
try {
loginContext.logout();
} catch (LoginException e) {
LOGGER.error("Failed to logout: {}.", e.getMessage());
}
}
}
The solution is not particularly useful, since next / the same user to log in will get NPE on the LoginContext.
I read that HttpServletRequest's getSession().invalidate(); suppose to call the logout() of JAAS or that HttpServletRequest's logout() would do the job. But both methods have no effect. For instance:
#RestController
#RequestMapping("/logout")
public class LogoutController {
private final LogoutService logoutService;
#Autowired
public LogoutController(LogoutService logoutService) {
this.logoutService = logoutService;
}
#DeleteMapping
public ResponseEntity<Void> deleteJwt(#CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
request.getSession().invalidate(); // logout() is not called.
request.logout(); // logout() is not called.
return getResponse();
}
}
I want to get the hand on the previously created LoginContext when a user wants to log out but create a new one when another user tries to log in.
Please note that I am not using Spring Security.
EDIT:
One of the ideas was to use a singleton that will hold a Set of login contexts associated with the particular user. And then call and destroy them when the user logs out. A key for such a Set could be a JWT token or user id. After further thinking, it appeared to me that a user might have multiple sessions, and in this case, user id as a key will fail to serve its purpose. The second option is a JWT token, but there is a scenario when the future middleware will issue a new JWT token upon expiration, then my Set will have no way to return a valid login context.
After some research, my team decided that JAAS doesn't suit our needs. We are not using the complete functionality it has to offer, and it ties our hands rather than smoothing the developing process.
If you will encounter a similar issue, here is an explanation:
we are using WebSphere 8.5.5 that has the support of JAAS. It is possible to logout, but the price will be tying it to the application server. Considering that in our plans is to move from WebSphere, this implementation is not an option.
The link to one of such guides lies here.
There are two alternatives for the future:
Wrap it in Spring Security since it offers support for JAAS;
Replace the custom module entirely relying on Spring Security's
functionality.

Spring Boot - MockMvc result null for GET (Cucumber)

I am running into an issue when trying to create a mockMvc get request, I receive a null result when requesting to get a JSON object. I have it that I can create a post fine, but struggling to receive data when invoking a GET endpoint in my controller.
AddressStepDefs.java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= WebEnvironment.MOCK)
#Transactional
#AutoConfigureMockMvc
/**
* Address Step Definition class to execute Scenario(s) contained in Address.feature
* #author Lewis Jones
*
*/
public class AddressStepDefs {
#Autowired
private WebApplicationContext wac;
#Autowired
private MockMvc mockMvc;
private ResultActions result;
#Autowired
#MockBean
private AddressRepository addressRepo;
/**
* Build the Controller under test
*/
#BeforeClass
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AddressController()).build();
}
/**
* Set the mock server up
*/
#Before
public void serverSetup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
/**
* Build the WebApplicationContext
*/
#Given("The server is up and running")
public void the_server_is_up_and_running() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#When("I request to view an Address with id {int} at {string}")
public void i_request_to_view_an_Address_with_id_at(Integer id, String request) throws Exception {
/** Build a GET request using mockMvc **/
result = this.mockMvc.perform(get(request + id).contentType(MediaType.APPLICATION_JSON));
}
#Then("the response code should be OK {int} and the resulting Address object json should be:")
public void the_response_code_should_be_OK_and_the_resulting_Address_object_json_should_be(Integer responseCode, String json) throws Exception {
result.andExpect(status().is(responseCode));
result.andExpect(content().string(json));
}
The Controller endpoint and the request is fine.
There is data in the database.
It works WITHOUT the #MockBean, but then my post actually inserts data into the database. (Which isn't what I want)
I have tried to #InjectMocks, no luck.
Where am I going wrong? Do I have the correct annotations?
By mocking your bean, you'll get all its results as null.
You have 2 options,
You continue to mock but you define behaviors using when/then
You spy your bean: then it will do "the same" as normal and you can just stub the method you don't want.

Null authentication #WithUserDetails in #RespositoryRestController

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.

spring mvc how to test that my service persists entities in post request

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.

Send request after success login with spring security

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.

Categories