Spring serving static content while having wildcard controller route - spring

My application is build using backbone on frontend and spring framework on backend. It is a single html application. Routes are handled by backbone, so I have a backend route with the next structure:
#RequestMapping(value="/**", method=RequestMethod.GET)
public String Pages()
{
return "index";
}
To point everything to my index.html. The thing is that the static content
files are pointed to this route too, and I don't want this. I've tried to
config WebMvcConfigurerAdapter by overriding addResourceHandler method for
static content, but it doesn't work.
#Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**").addResourceLocations("/resources/js");
}
}
How can I point every route to my index.html except /js/** and /assets/** ?
Thank you

The first thing is that your controller method that's mapped to /** will be taking priority over any resource requests. You can address this by increasing the precedence of ResourceHandlerRegistry. Add a call to registry.setOrder(Ordered.HIGHEST_PRECEDENCE) in the addResourceHandlers method of StaticResourceConfiguration:
#Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
registry.addResourceHandler("/js/**").addResourceLocations("/resources/js");
}
}
The second thing is that, by default, Spring Boot configures two resource handlers for you by default, one mapped to /** and one mapped to /webjars/**. Due to the change described above, this will now take priority over the method in your controller that's also mapped to /**. To overcome this, you should turn off default resource handling via a setting in application.properties:
spring.resources.addMappings=false

Related

Spring 5: How to load static resources (css, js, images)

I am upgrading a project from Spring 4 to spring 5 but loading static resources is not working.
I have my resources in
src/main/resources/static/js,
src/main/resources/static/css and
src/main/resources/static/images
I add a ResourceHandler in WebConfig as follows
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.job.controllers"})
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
// more code here
}
My security configuration to allow access to the static resources is as follows
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login**", "/static/**").permitAll()
}
}
When i access http://localhost:8080/static/css/file.css
I get the error 405 Request method 'GET' not supported
The problem seems not to be in the security config as it doesn't redirect me to the login page. If I try a none public URL like http://localhost:8080/some-place/css/file.css i get redirected to the login page.
The problem seems to be in the ResourceHandler.
My dependencies are:
spring-framework - 5.0.2.RELEASE and spring-security-5.0.0.RELEASE
None of the answers in the other questions work for me.
other-question
Thanks
I realised that when I commented out the #ComponentScan(basePackages = {"com.job.controllers"}) line on WebConfig class, the static resources would load.
This meant that that the problem was in one of my controllers.
One controller method was mapped #RequestMapping(params = "settings/view", method = RequestMethod.POST)
That mapping showed up as INFO Mapped "{[],methods=[POST],params=[settings/view]}" onto ..... in the startup logs
This was an incorrect mapping and it was stopping the static resources from loading.
When I corrected 'params' to 'value' like this => #RequestMapping(value = "settings/view", method = RequestMethod.POST) the static resources loaded.
Thanks

Changing default URL mapping for Serving Static Content in Spring Boot

My static resources stopped working as soon as I added a new Controller (non rest) in my application with the following mapping
#RequestMapping(value = "/{postId}/{postUri:.+}", method = RequestMethod.GET)
public String viewPost(#ModelAttribute("model") ModelMap model, PathVariable("postId") String postId, PathVariable("postUri") String postUri) {
// do something
}
After debugging I found that my newly added controller method started picking up static resources, basically, it has taken precedence over the default mapping for static resources.
For example, Request to the below static resource reaches my controller instead of static resource handler.
http://localhost:7999/css/bootstrap-2a31dca112f26923b51676cb764c58d5.css
I am using spring boot 1.4
Is there a way to modify the mapping URL for serving default static content since I do not want to modify the URL of my Controller method ?
Sure thing. There is a spring.mvc.static-path-pattern that you can use to override that:
spring.mvc.static-path-pattern=/resources/**
will map classpath:/static/css/foo.css to /resources/css/foo.css.
(I've made that clearer in a862b6d)
Having said that, I could only strongly recommend to change your path there. Having a path variable that catches the root context is really a bad idea.
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-static-content
By default Spring Boot will serve static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so you can modify that behavior by adding your own WebMvcConfigurerAdapter and overriding the addResourceHandlers method.
In a stand-alone web application the default servlet from the container is also enabled, and acts as a fallback, serving content from the root of the ServletContext if Spring decides not to handle it. Most of the time this will not happen (unless you modify the default MVC configuration) because Spring will always be able to handle requests through the DispatcherServlet.
By default, resources are mapped on /** but you can tune that via spring.mvc.static-path-pattern. For instance, relocating all resources to /resources/** can be achieved as follows:
spring.mvc.static-path-pattern=/resources/**
You can also customize the static resource locations using spring.resources.static-locations (replacing the default values with a list of directory locations). If you do this the default welcome page detection will switch to your custom locations, so if there is an index.html in any of your locations on startup, it will be the home page of the application.
In addition to the ‘standard’ static resource locations above, a special case is made for Webjars content. Any resources with a path in /webjars/** will be served from jar files if they are packaged in the Webjars format.
i dint use #EnableWebMVC. This worked for me and spring boot service server static content for default
localhost:8888/ and also for localhost:8888/some/path/
#Configuration
public static class WebServerStaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/some/path/").setViewName("forward:/index.html");
}
}
I added spring.resources.static-location=file:../frontend/build in application.properties
index.html is present in the build folder
Use can also add absolute path
spring.resources.static-location=file:/User/XYZ/Desktop/frontend/build
For no controller pages:
#Controller
#RequestMapping("/feature")
public class DataTableController {
// map /feature/* to /feature/*
#RequestMapping(value="/{name}", method = RequestMethod.GET)
public ModelAndView staticPage(#PathVariable String name){
return new ModelAndView("feature/" + name);
}
}
For static resource except for HTML:
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
// map /res/ to classpath:/resources/static/
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/res/**").addResourceLocations("classpath:/static/");
}
}

Spring catch all route for index.html

I'm developing a spring backend for a react-based single page application where I'm using react-router for client-side routing.
Beside the index.html page the backend serves data on the path /api/**.
In order to serve my index.html from src/main/resources/public/index.html on the root path / of my application I added a resource handler
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/").addResourceLocations("/index.html");
}
What I want to is to serve the index.html page whenever no other route matches, e.g. when I call a path other than /api.
How do I configure such catch-all route in spring?
Since my react app could use the root as forward target this ended up working for me
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/**/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
.setViewName("forward:/");
}
}
To be honest I have no idea why it has to be exactly in this specific format to avoid infinite forwarding loop.
I have a Polymer-based PWA hosted inside of my Spring Boot app, along with static web resources like images, and a REST API under "/api/...". I want the client-side app to handle the URL routing for the PWA. Here's what I use:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Ensure client-side paths redirect to index.html because client handles routing. NOTE: Do NOT use #EnableWebMvc or it will break this.
*/
#Override
public void addViewControllers(ViewControllerRegistry registry) {
// Map "/"
registry.addViewController("/")
.setViewName("forward:/index.html");
// Map "/word", "/word/word", and "/word/word/word" - except for anything starting with "/api/..." or ending with
// a file extension like ".js" - to index.html. By doing this, the client receives and routes the url. It also
// allows client-side URLs to be bookmarked.
// Single directory level - no need to exclude "api"
registry.addViewController("/{x:[\\w\\-]+}")
.setViewName("forward:/index.html");
// Multi-level directory path, need to exclude "api" on the first part of the path
registry.addViewController("/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}")
.setViewName("forward:/index.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
}
}
This should work for Angular and React apps as well.
Avoid #EnableWebMvc
By default Spring-Boot serves static content in src/main/resources:
/META-INF/resources/
/resources/
/static/
/public/
Take a look at this and this;
Or keep #EnableWebMvc and override addViewControllers
Did you specify #EnableWebMvc ? Take a look a this: Java Spring Boot: How to map my app root (“/”) to index.html?
Either you remove #EnableWebMvc, or you can re-define addViewControllers:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
Or define a Controller to catch /
You may take a look a this spring-boot-reactjs sample project on github:
It does what you want using a Controller:
#Controller
public class HomeController {
#RequestMapping(value = "/")
public String index() {
return "index";
}
}
Its index.html is under src/main/resources/templates
I use react and react-router in my spring boot app, and it was as easy as creating a controller that has mapping to / and subtrees of my website like /users/**
Here is my solution
#Controller
public class SinglePageAppController {
#RequestMapping(value = {"/", "/users/**", "/campaigns/**"})
public String index() {
return "index";
}
}
Api calls aren't caught by this controller and resources are handled automatically.
Found an answer by looking at this question
#Bean
public EmbeddedServletContainerCustomizer notFoundCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
}
};
}
Another solution (change/add/remove myurl1, myurl2, ... with your routes):
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
#Controller
public class SinglePageAppController {
/**
* If the user refreshes the page while on a React route, the request will come here.
* We need to tell it that there isn't any special page, just keep using React, by
* forwarding it back to the root.
*/
#RequestMapping({"/myurl1/**", "/myurl2/**"})
public String forward(HttpServletRequest httpServletRequest) {
return "forward:/";
}
}
Note: Using public String index() also works fine, but only if you use templates. And the use of WebMvcConfigurerAdapter is deprecated.
To answer your specific question which involves serving up the Single Page App (SPA) in all cases except the /api route here is what I did to modify Petri's answer.
I have a template named polymer that contains the index.html for my SPA. So the challenge became let's forward all routes except /api and /public-api to that view.
In my WebMvcConfigurerAdapter I override addViewControllers and used the regular expression: ^((?!/api/|/public-api/).)*$
In your case you want the regular expression: ^((?!/api/).)*$
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:^((?!/api/).)*$}").setViewName("polymer");
super.addViewControllers(registry);
}
This results in being able to hit http://localhost or http://localhost/community to serve up my SPA and all of the rest calls that the SPA makes being successfully routed to http://localhost/api/posts, http://localhost/public-api/posts, etc.
After lot of tries I've found the following solution as most simple one. It will basically bypass all the Spring handling which was so difficult to deal with.
#Component
public class StaticContentFilter implements Filter {
private List<String> fileExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico");
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = request.getServletPath();
boolean isApi = path.startsWith("/api");
boolean isResourceFile = !isApi && fileExtensions.stream().anyMatch(path::contains);
if (isApi) {
chain.doFilter(request, response);
} else if (isResourceFile) {
resourceToResponse("static" + path, response);
} else {
resourceToResponse("static/index.html", response);
}
}
private void resourceToResponse(String resourcePath, HttpServletResponse response) throws IOException {
InputStream inputStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream(resourcePath);
if (inputStream == null) {
response.sendError(NOT_FOUND.value(), NOT_FOUND.getReasonPhrase());
return;
}
inputStream.transferTo(response.getOutputStream());
}
}

Spring Boot url mappings order for controllers and static pages

I have a Spring Boot web application which is meant to serve both static and controller based (ModelAndView) pages. Problem is that a controller can serve something like /{string} and a static page must be served with /test.
The problem is that the controller mapping takes precedence, and I need to avoid that. If the user hits /test, he must be forwarded to the test.html static page.
I tried to use the order property of ViewControllerRegistry in this way, with no success:
#Configuration
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("forward:/test.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE); // but I tried with 0 and -1 as well: annotated controllers should have order equals to 0
}
}
This is my SpringBootApplication class:
#SpringBootApplication
public class VipApplication {
public static void main(String[] args) {
SpringApplication.run(VipApplication.class, args);
}
}
And this is the controller code:
#Controller
public class VipController {
#RequestMapping(value = "/{string}")
public ModelAndView vip(#PathVariable("string") String string) {
ModelAndView mv = new ModelAndView("mypage");
return mv;
}
}
How can I reorder the mappings to make sure static pages are considered before annotated controllers?
(I'm not sure, but) I suggest to override WebMvcConfigurerAdapter.addResourceHandlers() method and configure order of resource handler by invoking ResourceHandlerRegistry.setOrder()

Spring security - Access to a controller method based on an attribute

I'm configuring Spring Security across all my controllers.
I want some method executions to start only when "my system is enabled". This information is accessible from all over the controllers via a specific static method (I can make it non-static).
My point is that I want to avoid making an explicit check in java code at the beginning of every method.
How can I get there via Spring Security?
One approach is to use a handler interceptor.
Here is general idea:
(1) Configure url patterns which you want to block:
<util:list id="sysEnableCheckUrlPatterns" value-type="java.lang.String">
<beans:value>/module1/**</beans:value>
<beans:value>/module2/**</beans:value>
</util:list>
(2) Write an interceptor:
public class SysEnableCheckInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
/*
If system enabled then return true. Otherwise return false (and optionally write something in response)
*/
}
}
(3) Configure that interceptor. In 3.1 you can do it as follows:
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
#Resource(name="sysEnableCheckUrlPatterns")
/* or use #Autowired or #Inject if you like */
private String[] sysEnableCheckUrlPatterns;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SysEnableCheckInterceptor()).addPathPatterns(sysEnableCheckUrlPatterns);
}
}
You can use SPEL (Spring Expression Language) in a security annotation.
See http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html

Resources