I try to test my security layer using MockMvc. I've write the following integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ApplicationContextConfig.class,WebSecurityConfig.class})
#WebAppConfiguration
public class AuthenticationStatesIT {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void initMocks(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(new AuthenticationFilter(), "/*")
.build();
}
#Test
public void stage10_firstRequestForLoginPageShouldReturnProperPageAndAddUnauthenticatedStateToSession () throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/"))
.andDo(print())
//.andExpect(cookie().exists("JSESSIONID"))
.andExpect(status().is3xxRedirection()).andReturn();
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
StatesAuthenticator authenticator = (StatesAuthenticator)session.getAttribute("authenticator");
AuthenticationState state = authenticator.getState();
Assert.assertNotNull(authenticator);
Assert.assertNotNull(state);
}
}
Everything work ok except one detail. The 'JSESSIONID' cookie is not creating. I am sure that the new session is created but the test 'andExpect(cookie().exists("JSESSIONID"))' is not passed. I am creating session as follows:
public class UnauthenticatedState implements AuthenticationState {
#Override
public void doAuthentication(StatesAuthenticator authentication,ServletRequest request,
ServletResponse response,FilterChain chain) throws IOException, ServletException {
authentication.setAuthentication(null);
HttpServletResponse httpResponse = (HttpServletResponse)response;
HttpServletRequest httpRequest = (HttpServletRequest)request;
//get the old session and invalidate if exists
HttpSession oldSession = httpRequest.getSession(false);
if (oldSession != null) {
oldSession.invalidate();
}
//generate a new session
HttpSession session = httpRequest.getSession(true);
session.setMaxInactiveInterval(300); // 5 minutes
session.setAttribute("authenticator", authentication);
authentication.setState(new AuthenticatingState());
httpResponse.sendRedirect("login");
}
}
When I run server and look for that cookie in browser everything is ok, the cookie exists. Can someone explain me why MockMvc do no set 'JSESSIONID'? Thanks for any help!
Related
The following test fails when I try to integrate spring session.
class WeChatOAuth2AuthenticationFilterTest extends AbstractWebMvcTest {
#Test
void it_should_redirect_user_to_origin_uri_when_wechat_oauth_is_finished() throws Exception {
String code = "codeToExchangeWeChatUserAccessToken"
String plainUrl = "http://www.example.com/index.html?a=b#/route"
String state = Base64.getUrlEncoder().encodeToString(plainUrl.getBytes("UTF-8"))
WxMpOAuth2AccessToken accessToken = new WeChatUserOAuth2AccessTokenFixture().buildToken()
given(wxMpService.oauth2getAccessToken(code))
.willReturn(accessToken)
this.mockMvc.perform(get("/wechat/oauth/token")
.param("state", state)
.param("code", code))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(plainUrl))
.andExpect(authenticated())
// throws Authentication should not be null
}
}
#Configuration
#EnableSpringHttpSession
public class HttpSessionConfig {
#Bean
protected SessionRepository sessionRepository() {
return new MapSessionRepository();
}
}
After some debugging, I find out that it is probably due to I cannot get HttpSession
// org.springframework.security.web.context.HttpSessionSecurityContextRepository
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
//returns null with spring-session,
//returns a MockHttpSession instance without spring-session
SecurityContext context = readSecurityContextFromSession(httpSession);
Currently, I make the spring session disabled for the tests with #ConditionalProperties. Any better idea is welcome.
This is related to correct setup of you mockMvc object in your test.
For brevity, I assume you can use #SpringBootTest annotation in your project. The codes below shows how you could properly wire in spring-session related classes into your mockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ExampleControllerV2SpringSessionTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private SessionRepository sessionRepository;
#Autowired
private SessionRepositoryFilter sessionRepositoryFilter;
//this is needed to test spring-session specific features
private MockMvc mockMvcWithSpringSession;
#Before
public void setup() throws URISyntaxException {
this.mockMvcWithSpringSession = MockMvcBuilders
.webAppContextSetup(wac)
.addFilter(sessionRepositoryFilter)
.build();
}
//------------------------- BEGIN: Test cases with Spring-session---------------------------------
#Test
public void getANewSpringSession(String requestBody) throws Exception {
MvcResult result = mockMvcWithSpringSession.perform(
get("/v1/sampleendpoint") .header("YOUR_HEADER_NAME", "YOUR_HEADER_VALUE")
.contentType(MediaType.APPLICATION_JSON)
.content("YOUR_SAMPLE_JSON_BODY"))
.andExpect(status().isOk())
.andExpect(header().string("x-auth-token", notNullValue()))
.andReturn();
String xAuthToken = result.getResponse().getHeader(AuthenticationControllerV2.Params.SESSION_KEY);
MapSession curSession = (MapSession) sessionRepository.getSession(xAuthToken);
Assert.assertNotNull(curSession);
}
//------------------------- END: Test cases with Spring-session---------------------------------
}
I am trying to write a junit for a spring controller whose signature is something like this
#RequestMapping(value = { "/addPharmcyInLookUpTable.form" }, method = { org.springframework.web.bind.annotation.RequestMethod.POST })
public String processSubmitAddPhl(#ModelAttribute PhrmcyAdmin phrmcyAdmin,
BindingResult result, SessionStatus status,
HttpServletRequest request) throws Exception {
.....
....
}
The junit for this is
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/applicationContext.xml",
"classpath:/puela-app-config.xml" }, inheritLocations = true)
public class AddPharmacyInLookUpTableControllerTest {
public static junit.framework.Test suite() {
return new JUnit4TestAdapter(
AddPharmacyInLookUpTableControllerTest.class);
}
#InjectMocks
private AddPharmacyInLookUpTableController controller;
private static MockHttpServletRequest request;
private static MockHttpServletResponse response;
#Autowired
private HandlerMapping handlerMapping;
#Autowired
private HandlerAdapter handlerAdapter;
#BeforeClass
public static void runBeforeAllTest() throws Exception {
System.out.println("Running one time Setup");
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
private ModelAndView handle(final HttpServletRequest request,
final HttpServletResponse response) throws Exception {
final HandlerExecutionChain handler = handlerMapping
.getHandler(request);
Assert.assertNotNull(
"No handler found for request, check you request mapping",
handler);
final Object controller = handler.getHandler();
for (final HandlerInterceptor interceptor : handlerMapping.getHandler(
request).getInterceptors()) {
if (!interceptor.preHandle(request, response, controller)) {
return null;
}
}
return handlerAdapter.handle(request, response, controller);
}
#Test
public void processRequestAddPhl_post() throws Exception
{
PhrmcyAdmin phrmcyAdmin = new PhrmcyAdmin();
phrmcyAdmin.setPhlCalMailbox("Test");
phrmcyAdmin.setPhlMailPharmacy("FootHill");
request.setMethod("POST");
request.setRequestURI("/addPharmcyInLookUpTable.form");
// Code goes here
MockHttpSession session = new MockHttpSession();
ModelAndView mv = handle(request, response);
assertEquals(mv.getViewName(), "addPhrmcyInTable.view");
}
}
I am trying to send this model object phrmcyAdmin along with the request. Any idea how we can deal with the model object??
I'm new to spring mvc , I'm working on a web project admin panel.
Here is some example of my admin pages controllers :
#Controller
#RequestMapping("/admin/article/**")
public class ArticleController {
private ArticleDao articleDao;
private String fileName;
private String baseUrl;
public ArticleController() {
articleDao = ArticleDaoFactory.create();
}
#RequestMapping(value = "/admin/article",method = RequestMethod.GET)
public String doGet(ModelMap model,HttpServletRequest request,ArticleForm articleForm) {
//some codes
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.GET)
public String doGetAdd(ModelMap model,ArticleForm articleForm) {
model.addAttribute("article", articleForm);
return "admin/articleAdd";
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.POST)
public String doPost(#ModelAttribute ArticleForm article, BindingResult result ,ModelMap model){
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.GET)
public String getEdit(ModelMap model, #PathVariable("id") int id) {
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.POST)
public String postEdit(ModelMap model, #PathVariable("id") int id, ArticleForm article, BindingResult result) {
//some codes
}
#RequestMapping(value = "/admin/article/delete/{id}",method = RequestMethod.GET)
public void getDelete(ModelMap model, #PathVariable("id") int id, HttpServletResponse response) {
//some codes
}
}
now I need another mapping in another contoller named AdminController (for example) to Authenticate admin and bring him to login page if he is not logged in. for sure Authenthication is one example, I might want to use more classes on every admin page.
Note that my authenthication class needs request and session references (and for sure my other classes will need other references created by spring)
I got to know that I can not get HttpServletRequest and ... using a constructor method so I wrote another request mapping to call a method.
Eventhough I can set my properties this way ,but I can not use this method on every admin url.
#Controller
#RequestMapping(value = "/admin/**",method = RequestMethod.GET)
public class AdminController {
Authentication authentication;
HttpServletRequest request;
HttpSession session;
HttpServletResponse response;
public void checkAndSet(HttpSession session,HttpServletRequest request,HttpServletResponse response) {
authentication = new Authentication(session,request);
this.request = request;
this.session = session;
this.response = response;
if(!authentication.isLoggedIn()){
System.out.println(" I'm not logged in");
response.setHeader("Location","/admin/login");
}
}
So I need some suggestion on how to write a request mapping in a controller to call a method on every other controllers that are 'admin' page child ?
FYI : I'm not thinking for spring security for this.
thanks;
I think you can do it by implementing a servlet filter.
For example :
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getServletPath();
HttpSession session = request.getSession(false);
Authentication authentication = new Authentication(session,request);
if (isAdminUrl(url) && !authentication.isLoggedIn()) {
res.sendRedirect/admin/login");
}
chain.doFilter(req, res);
}
}
And then, you have to implement the method isAdminUrl(String url) to determine if you want to apply your filter.
Otherwise, I strongly recommend you to take a look at Spring Security
I want to test my Servlet with different incoming URLs. I tried to use Mockito to test if specific function was called:
package servlet;
import blabla;
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(locations = {"classpath:application-context-test.xml"})
public class MainServletTest extends TestCase{
#Autowired
private Categories categories;
private MockHttpServletRequest request = new MockHttpServletRequest();
#Mock
private HttpServletResponse response;
#Mock
private HttpSession session;
#Mock
private RequestDispatcher rd;
#Test
public void testCategories() throws ServletException, IOException {
// given
request.setServerName("localhost");//here I try to change incoming URL
request.setRequestURI("/test/categories");//here I try to change incoming URL
HttpServletRequest req = spy(request);//???
//when
new MainServlet().doGet(req, response);
//then
verify(req).setAttribute("categories", categories.getContainer());//here try to check if this method is called
}
}
Here I try to change incoming url and check if specific attribute was set for incoming request. Since req is not Mock object but MockHttpServletRequest object - this code does not work. Any ideas?
Either use a mock:
// ...
#Mock
private HttpServletRequest request;
// ...
#Test
public void testCategories() throws ServletException, IOException {
// given
when(request.getServerName()).thenReturn("localhost");
when(request.getRequestURI()).thenReturn("/test/categories")
//when
new MainServlet().doGet(req, response);
//then
verify(req).setAttribute("categories", categories.getContainer());
or
Use MockHttpServletRequest to check the attribute named categories:
assertEquals(categories.getContainer(), req.getAttributes("categories"));
I just discovered the new Spring Security 4 test annotation #WithMockUser, but I cannot have it working for my Selenium test.
The thing is, this annotation creates a mock SecurityContext, but a new one is created by the HttpSessionSecurityContextRepository because Selenium runs the test based on a real browser.
Could I somehow tell Spring to use the mock SecurityContext as the next session security context?
Thanks!
I found to way to authenticate a mock user with a new filter in the test classpath:
#Component
public class MockUserFilter extends GenericFilterBean {
#Autowired
private UserDetailsService userDetailsService;
private SecurityContext securityContext;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (securityContext != null) {
SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request);
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response);
securityContextRepository.loadContext(requestResponseHolder);
request = requestResponseHolder.getRequest();
response = requestResponseHolder.getResponse();
securityContextRepository.saveContext(securityContext, request, response);
securityContext = null;
}
chain.doFilter(request, response);
}
public void authenticateNextRequestAs(String username) {
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
}
}
It is inspired from SecurityMockMvcRequestPostProcessors and WithUserDetailsSecurityContextFactory.
I could not use #WithUserDetails annotation because I run Cucumber tests, but with this filter I can mock an authentication for the next request in one line: testSecurityFilter.authenticateNextRequestAs("username");
I'm adding this answer because while the accepted answer helped me in forming a solution, I had to make some changes to get this to work. This answer also helped me in getting it working: https://stackoverflow.com/a/8336233/2688076
Here is my MockUserFilter:
#Component("MockUserFilter")
public class MockUserFilter extends GenericFilterBean {
#Autowired
private UserDetailService userDetailService;
private SecurityContext securityContext;
#Autowired
private AuthenticationProvider authenticationProvider;
public void setUserDetailService(UserDetailService userDetailService) {
this.userDetailService = userDetailService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
if (securityContext != null) {
SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(servletRequest);
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(servletRequest, servletResponse);
securityContextRepository.loadContext(requestResponseHolder);
servletRequest = requestResponseHolder.getRequest();
servletResponse = requestResponseHolder.getResponse();
securityContextRepository.saveContext(securityContext, servletRequest, servletResponse);
securityContext = null;
}
chain.doFilter(request, response);
}
public void authenticateNextRequestAs(String username, ServletRequest request) {
UserDetails principal = userDetailService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = ((HttpServletRequest) request).getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
}
In addition to this I also had to remove my casAuthenticationFilter from the filter chain to get this working. I use a properties value to enable/disable this.
I'm relatively new to Spring and Spring security so any comments on this solution are welcome. I'm not sure how "good" or "bad" this solution is.
One thing to keep in mind is that this is a solution for local testing or testing in a secure environment, not one that you'd want in a dev environment.