Spring MockMvc redirect not working - spring-mvc

I am trying to mock a post request using the below code .
I am testing a spring security login request.
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).addFilter(springSecurityFilterChain)
.apply(springSecurity())
.build();
MvcResult mvcResult = mvc.perform(post("/j_spring_security_check?api=true").param("userName", USERNAME)
.param("password", PASSWORD)).andReturn();
The code works fine and after successful login , I am redirecting to another controller.
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
RequestDispatcher rd = request.getRequestDispatcher("/myURL");
rd.forward(request, response);
}
When I run the test , I get the below logs. The redirect does not happen and the controller mapped to /myURL is not getting invoked.
11:59:55.839 [main] DEBUG o.s.mock.web.MockRequestDispatcher - MockRequestDispatcher: forwarding to [/myURL]
11:59:55.841 [main] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - The HttpSession is currently null, and the HttpSessionSecurityContextRepository is prohibited from creating an HttpSession (because the allowSessionCreation property is false) - SecurityContext thus not stored for next request
11:59:55.841 [main] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
There is no error reported .
Am I missing something ? Will redirect not happen while using MockMvc ?

Will redirect not happen while using MockMvc ?
NO.
The following is taken straight from the MockMvc vs End-to-End Tests section of the reference manual.
Things that may catch you by surprise are that there is no context path by default, no jsessionid cookie, no forwarding, error, or async dispatches, and therefore no actual JSP rendering. Instead, "forwarded" and "redirected" URLs are saved in the MockHttpServletResponse and can be asserted with expectations.
Thus, all you can do is verify that a forward would take place in a real Servlet Container, and you do that by invoking andExpect(forwardedUrl("/myURL")) instead of andReturn().
Note that forwardedUrl is a static method in MockMvcResultMatchers.

Related

Why is Spring exception handler not working as expected

When I use #ExceptionHandler within Controller, it's not working as expected.
Here is my code:
#Controller
public class PageController {
#RequestMapping("/page")
public ModelAndView index(ModelAndView modelAndView){
String mess = null;
mess.split(",");
modelAndView.setViewName("index");
return modelAndView;
}
#ExceptionHandler({Exception.class})
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Request")
public ModelAndView handleException(Exception ex, HttpServletRequest request, HttpServletResponse response){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", ex.getMessage());
modelAndView.addObject("url", request.getRequestURL());
modelAndView.addObject("code", response.getStatus());
modelAndView.setViewName("exception");
return modelAndView;
}
}
After the application starts with debug mode, I visit http://localhost:8080/page, and handleException is running, but the view is below other than the expected excepction view. Why?
The problem is with the #ResponseStatus annotation. Have a look at the following article: http://blog.sizovs.net/spring-rest-exception-handler/. In the mid of this article the author says following:
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a org.springframework.http.ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
According to a Spring article: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc, Spring MVC chains the following three resolvers in the order below:
ExceptionHandlerExceptionResolver matches uncaught exceptions against for suitable #ExceptionHandler methods on both the handler (controller) and on any controller-advices.
ResponseStatusExceptionResolver looks for uncaught exceptions annotated by #ResponseStatus (as described in Section 1)
DefaultHandlerExceptionResolver converts standard Spring exceptions and converts them
to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC).
So the ResponseStatusExceptionResolver is triggered after the ExceptionHanlderExceptionResolver and uses the default and will display Spring's error page.
For a quick fix try to remove #ResponseStatus and you should see your custom error page in your browser.

Spring Boot - MockMVC forwardedUrl using Thymeleaf

I have a basic SpringBoot app. using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this controller:
#Controller
#RequestMapping("/deviceevent")
public class DeviceEventController {
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
public String deviceeventList() {
return "tdk/deviceEvent/DeviceEventList";
}
}
and this other test class. Tests using Spring's MockMVC framework. This drives an MVC application in a test, as if it was running in a container,
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
// Pull in the application context created by #ContextConfiguration
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
// Setup MockMVC to use our Spring Configuration
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list") //
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk()) //
.andExpect(model().size(1)) //
.andExpect(forwardedUrl("tdk/deviceEvent/DeviceEventList"));
}
But I got this error in the forwarded URL. I always used this method in JSP, never in Thymeleaf, but I guess that It is the same:
java.lang.AssertionError: Forwarded URL expected:</tdk/deviceEvent/DeviceEventList> but was:<null>
Assuming a standard Thymeleaf/Spring setup, it looks like there is a misunderstanding with what the controller is doing - when the controller returns that string "tdk/deviceEvent/DeviceEventList" it is not forwarding the HTTP request somewhere, but returning a view name.
With a normal Spring-thymeleaf setup, that string corresponds to the name of a thymeleaf view that will be rendered on hitting that endpoint (I assume the controller is just serving a normal webpage - so that path probably corresponds to some file path most likely in src/main/resources - but again, this depends a lot on your spring config) - at this point the HTTP request has not been returned to the user, and Spring is still processing it - and will attempt to render the HTML view before returning to the user.
The forwarded URL is used if Spring is not rendering anything but instead returning a HTTP response to the user to forward them to another URL (which will start a different Spring request-response process) using a 301/302 mechanism.
Note the difference in the following methods:
#RequestMapping( value="/document", method=RequestMethod.GET )
public String newDocumentSettings( Model model ){
model.addAllAttributes( contentManagementService.documentToJson() );
return "pages/document-settings";
}
#RequestMapping( value="/document", method=RequestMethod.POST )
public String createNewDocument( #RequestParam String title, #RequestParam String overview, #RequestParam String tags ){
Document doc = documentService.createDocument( title, overview, tags );
return "redirect:/document/${doc.url}/1?getting-started";
}
The first renders the template at the given filepath, the second returns a redirect command to the browser to make another HTTP request to the given URL.
In any case, the forwardedUrl in your test case is because hte HTTP Response doesn't have a URL to forward to (because its returning the HTML). If you do want forwarding behaviour (e.g. you actually want to complete the response and the browser to make a second HTTP request) then you would likely need to update the controller as per example, however, if you are happy with the rendered html page, then the test is invalid (look at the Thymeleaf testing framework to see how to test templating).
Caveat: This is based on the assumption of default Spring-Boot config - if you have other config whereby that string does result in a forwarded HTTP request then this doesnt apply!
Taking a guess here, but the URL tdk/deviceEvent/DeviceEventList is probably not defined. Try replacing it with the URL associated with your context (edit as necessary):
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list")
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(forwardedUrl("/WEB-INF/tdk/deviceEvent/DeviceEventList.html"));
}
Aside, instead of:
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
you can use the shorthand:
#GetMapping("/list")

How to redirect to URL unless some conditions is not met using HandlerInterceptor in a Spring Boot Web MVCApp

I am using Spring Boot for a WebMVC App.
There are many url that i serve in my app like
/company
/level
/student
etc.
Problem is
How do i redirect to '/company' whenever any request is made and company has not yet been register.
I have tried using HandlerInterceptor like this
public class CompanySetupInterceptor implements HandlerInterceptor {
Logger logger = Logger.getLogger(CompanySetupInterceptor.class);
CurrentUser currentUser;
#Autowired
CompanyService companyService;
#Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
/*check company is registered or not
* if not redirect to /company
* */
if(companyService.getCompany() == null){
httpServletResponse.sendRedirect("/company");
return false;
}
else{
return true;
}
}
But every time i implement it,it goes into infinite loop from redirect to intercept and so on.
Localhost redirected you too many times.
Localhost page is not working
How do i achieve solution to above problem?
Interceptor you created is called for every incoming request and as you are redirecting to /company from your interceptor,its again an incoming request for spring mvc and is being forwarded again to interceptor thats why its going in infinite loop.
you need to configure your interceptor to bypass its execution for specific URL's like /company in your case.
Refer below link to configure interceptor
http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-config-interceptors

JAAS Custom Login Module with Wildfly

I'm trying to develop a custom JAAS login module, which consumes a token and get's the user data from different sources.
It should work as an authentication realm for wildfly 8.2.1 final. I've got a test servlet in which I want to authenticate so the allowed roles for a injected EJB is checked.
The test from this site: http://www.radcortez.com/custom-principal-and-loginmodule-for-wildfly/ In fact I even started with his git project.
Because I only use a token, I can not use request.login(username, password) to initiate the wildfly login process. So I tried this:
#Inject
private SampleEJB sampleEJB;
...
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = request.getParameter("token");
try{
context = new LoginContext("TokenLoginDomain", new TokenCallbackHandler(token));
context.login();
}catch(Exception e){
e.printStackTrace();
}
response.getWriter().println("username=" + exampleEJB.getPrincipalName());
}
My EJB looks like this:
#Stateless
public class SampleEJB {
#Resource
private EJBContext ejbContext;
#RolesAllowed("partner")
public String getPrincipalName() {
return ejbContext.getCallerPrincipal().getName();
}
}
How do I start the login process without credentials in a servlet, so the user is logged in as wildfly user?
You can't authenticate that way in Java EE. With the given code you'll only be authenticated within the LoginContext instance.
As you already found out request.login(username, password) triggers the authentication process, but it's limited to the username/password credential.
In this case you should not only write a custom LoginModule (identity store), but a custom authentication mechanism as well. Authentication mechanisms are the controllers so to speak in the authentication process, while the identity store is the model.
Custom authentication mechanisms can be added via the JASPIC SPI, which WildFly supports well.
You can register a ServerAuthModule (SAM) from the app with some utility code that you can copy from here: https://github.com/arjantijms/glassfish-sam-ee-namespaces/tree/master/src/main/java/javax/security/authenticationmechanism
Then actually register the SAM with code as shown here:
https://github.com/arjantijms/glassfish-sam-ee-namespaces/blob/master/src/main/java/test/SamAutoRegistrationListener.java

Serve login page JSP on failed login attempt

My login form is located at /account/login. GET requests to this URL cause a request handler to be invoked, which returns account/login as the logical view name, and /WEB-INF/jsp/account/login.jsp gets served.
#RequestMapping(value="/account/login", method=RequestMethod.GET)
public String login()
{
return "account/login";
}
POST requests to /account/login invoke my UsernamePasswordAuthenticationFilter, so the login form is both served by (via GET) and submits to (via POST) /account/login.
This all works great.
However, in the event of a login failure I would like /WEB-INF/jsp/account/login.jsp to be served without doing an HTTP redirect, i.e. POSTing a bad username/password to /account/login would serve /WEB-INF/jsp/account/login.jsp.
I've tried overriding SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure() like so:
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
{
request.getRequestDispatcher("account/login").forward(request, response);
}
However, this results in a 404 response when POSTing a bad username/password to /account/login.
I've tried passing in /WEB-INF/jsp/account/login.jsp, /account/login.jsp, and many other combinations to ServletRequest.getRequestDispatcher() but nothing seems to work.
Any ideas what I'm doing wrong? I have a feeling the dispatcher is trying to dispatch to my request handler rather than the JSP itself, but I'm not familiar enough with the entire JSP dispatch workflow to know how to get around that.
I accidentally figured this out shortly after posting the question. Answering here in case it becomes of some use to someone else down the road.
In the onAuthenticationFailure(), I passed /account/login to getRequestDispatcher(), and I also called include() rather than a forward(), like so:
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
{
request.getRequestDispatcher("/account/login").include(request, response);
}
Submitting the login form with an incorrect username and/or password causes the login form JSP to be served as the POST's response, and submitting the correct username/password still invokes my UsernamePasswordAuthenticationFilter!!

Resources