I wrote a small application to learn the java configuration in spring as I have been nagged by peers for a while now to upgrade our applications ;-), a simple todo list app, which has security and web mvc configuration, JPA for persistence, all through the java configuration. I am facing an issue when trying the run the application. The scurity config and JPA etc work fine but I get a null view after successful intercept of protected URLs
The main web app initializer class extends AbstractAnnotationConfigDispatcherServletInitializer
public class WiggleWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { WiggleApplicationConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WiggleWebAppConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected void registerDispatcherServlet(ServletContext servletContext) {
super.registerDispatcherServlet(servletContext);
servletContext.addListener(new HttpSessionEventPublisher());
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[] { characterEncodingFilter };
}
}
the WiggleApplicationConfig
imports security, JPA and social
@Configuration
@ComponentScan(basePackages = { "wiggle.app.services.*" })
@Import({ WigglePersistenceConfig.class, WiggleSecurityConfig.class,
WiggleSocialConfig.class })
public class WiggleApplicationConfig {
@Bean
public DateFormat dateFormat() {
return new SimpleDateFormat("dd-MM-yyyy");
}
}
The web config then adds default handler and the like
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "wiggle.app.controllers.*" })
public class WiggleWebAppConfig extends WebMvcConfigurerAdapter {
private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/jsp/";
private static final String VIEW_RESOLVER_SUFFIX = ".jsp";
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations(
"/static/");
}
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties exceptionMappings = new Properties();
exceptionMappings.put("java.lang.Exception", "error/error");
exceptionMappings.put("java.lang.RuntimeException", "error/error");
exceptionResolver.setExceptionMappings(exceptionMappings);
Properties statusCodes = new Properties();
statusCodes.put("error/404", "404");
statusCodes.put("error/error", "500");
exceptionResolver.setStatusCodes(statusCodes);
return exceptionResolver;
}
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);
return viewResolver;
}
}
All of this resides in package wiggle.app.config
, going by my configuration /**
is protected and should redirect to /login, which is open for all, the security filter chain does work all right, I see Access Denied after which there is redirection to /wiggle/login how ever I get a 404 after that with following log entries when I access the home page i.e. http://localhost:8080/wiggle/
Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@6faeba70: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffbcba8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 8A7C29831E56336A6FDF1A0E19200E70; Granted Authorities: ROLE_ANONYMOUS
Voter: org.springframework.security.web.access.expression.WebExpressionVoter@c01ac1b, returned: 1
Authorization successful
RunAsManager did not change Authentication object
/login reached end of additional filter chain; proceeding with original chain
DispatcherServlet with name 'dispatcher' processing GET request for [/wiggle/login]
Looking up handler method for path /login
Did not find handler method for [/login]
Matching patterns for request [/login] are [/**]
URI Template variables for request [/login] are {}
Mapping [/login] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@688a42b5] and 1 interceptor
Last-Modified value for [/wiggle/login] is: -1
SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
Null ModelAndView returned to DispatcherServlet with name 'dispatcher': assuming HandlerAdapter completed request handling
Successfully completed request
Chain processed normally
SecurityContextHolder now cleared, as request processing completed
I would usually put the following in an XML to take care of mappings
<beans:bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<!-- Enables annotated POJO @Controllers -->
<beans:bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
and
<!-- Scans within the base package of the application for @Components to configure as beans -->
<context:component-scan base-package="com.code.controller" />
I am not able to find out what am I doing missing to enable similar behaviour with Java Configuration.
In Spring-MVC when you write annotation like @Controller, indirectly you are using a Servlet called Dispatcher Servlet. Dispatcher Servlet is defined in web. xml file with properties and class name which is mapped to . jsp pages and Controller part.
The Spring Boot starters ( spring-boot-starter-web in particular) use Tomcat as an embedded container by default.
The DispatcherServlet is the front controller in Spring web applications. It's used to create web applications and REST services in Spring MVC. In a traditional Spring web application, this servlet is defined in the web. xml file.
The default servlet is the servlet which serves static resources as well as serves the directory listings (if directory listings are enabled). Where is it declared? It is declared globally in $CATALINA_BASE/conf/web.xml.
Turns out I missed an important piece of documentation w.r.t. this configuration, section 16.16.8 mvc:default-servlet-handler from the Spring Framework Docs
The caveat to overriding the "/" Servlet mapping is that the RequestDispatcher for the default Servlet must be retrieved by name rather than by path. The DefaultServletHttpRequestHandler will attempt to auto-detect the default Servlet for the container at startup time, using a list of known names for most of the major Servlet containers (including Tomcat, Jetty, GlassFish, JBoss, Resin, WebLogic, and WebSphere). If the default Servlet has been custom configured with a different name, or if a different Servlet container is being used where the default Servlet name is unknown, then the default Servlet’s name must be explicitly provided as in the following example:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("myCustomDefaultServlet");
}
}
hence I changed to this
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable("wiggleServlet");
}
There was another piece of misconfiguration
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "wiggle.app.controllers.*" })
public class WiggleWebAppConfig extends WebMvcConfigurerAdapter {
should be
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "wiggle.app.controllers" })
public class WiggleWebAppConfig extends WebMvcConfigurerAdapter {
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With