Spring app with React UI - how to serve when #EnableWebMvc is applied? - java

So I have a React app I want to serve from my Spring app (ala this blog). As part of my gradle build task, I run the npm build command and copy the resulting files to /build/resources/main/static. This works fine and I can access my app at mysite.com/index.html, but I want to control who has access more granularly. As such, I applied #EnableWebMvc to my app, but from there, I can't seem to get my API controller to actually serve the view from the build directory. It seems no matter where I put it, it doesn't like serving directly from /build. Any way to make this work?
The handler looks like:
#Controller
class MyController {
#RequestMapping("/")
fun index(): String {
return "index"
}
}

As indicated in the Spring Boot documentation, you do not need - in fact, it is not recommended - to use #EnableWebMvc when using Spring Boot. They state, when describing Spring MVC auto-configuration:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
And:
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own #Configuration class of type WebMvcConfigurer but without #EnableWebMvc.
In the guide, they continue when describing static content handling:
By default, Spring Boot serves 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 that you can modify that behavior by adding your own WebMvcConfigurer and overriding the addResourceHandlers method.
In your example, following this advice, you can indicate the static resource handling location with something like (sorry, I am not fluent in Kotlin, forgive for write the example in Java):
#Controller
public class MyController implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static")
;
}
#GetMapping(path = "/")
public String index() {
return "index";
}
}
Please, adapt the paths in addResourceHandlers to your needs.
You can of course place this method in an ad hoc #Configuration.
Having said that, if when you say granular you mean security, the best approach you can take is to configure Spring Security and provide the necessary authorization rules: please, see the relevant documentation.

Related

is it possible to have Spring configuration relying only on annotations apart from being spring boot application?

I'm new in Spring applications, and see the big difference between configurations in springBoot and spring. So my questin is: apart from spring-boot, is there a way to setup a proper spring application(with web mvc, security, aop, ...), without any xml config file (ie : config relying only on annotations).
Yes, there is a way to do this in Spring. Spring Boot is after all an enhanced, autoconfigured Spring (with other cool features). That means that everything there is in Spring Boot should be achievable in Spring as well, but you would have do a bit/a lot of Your own extra work.
Moving straight to the point, in order to achieve what you want, you would need to undertake the following steps:
Create a class, which will store all the configuration (basically the properties you would store in the xml file) - let's call it AppConfig.class
Annotate the AppConfig.class with #Configuration - this will inform Spring that this class is the source of configuration;
Annotate the AppConfig.class with #ComponentScan("com.app") - here, You need to provide a package, from which Spring has to start component scanning in order to find Beans to be registered in Spring Container. Important note is, that it will scan the package and it's subpackages, so you would mostly want to provide here the top level package;
If you need some data to be injected into your beans, you would want to use the #PropertySource("classpath:application.properties") - I have provided here the default value, which Spring Boot uses internally in case you want to inject some data into your beans at runtime. For this to work, you need to inject into AppConfig.class an Environment.class
To show it on the example:
#Configuration
#ComponentScan("com.app")
#PropertySource("classpath:application.properties")
public class AppConfig {
// it will help to pull the properties incorporated in the file you have provided in the #PropertySource annotation
private Environment environment;
//inject it
public AppConfig(Environment environment) {
this.environment = environment;
}
// build your beans - the getProperty method accepts the key from application.properties
// file and return a value as a String. You can provide additional arguments to convert
//the value and a default value if the property is not found
#Bean
public Product product() {
return new Product(
environment.getProperty("product.name", "XXX"),
environment.getProperty("product.price", BigDecimal.class, BigDecimal.ZERO),
environment.getProperty("product.quantity", Integer.class, 10)
);
}
}
I hope that it helps

Spring Boot 2 index.html not loaded automatically from subdirectory mapped as static resource

I have a Maven module containing my Angular 6 application, and at build it is packaged in a jar at META-INF/resources/admin/ui.
My Spring Boot 2 application has a dependency to the frontend Maven module and when building it includes the frontend library as well. However, if I access http://localhost:8080/admin/ui/ it downloads an empty ui file, but if I access http://localhost:8080/admin/ui/index.html then it displays the Angular application.
If I package the frontend application at META-INF/resources/ then http://localhost:8080/ will display the Angular application correctly, but I want the context of the frontend application to start from /admin/ui. The Spring Boot application does not have any custom mappings, it is just annotated with
#Configuration
#EnableAutoConfiguration
#EnableScheduling
#ComponentScan(basePackageClasses = {...})
#Import({...})
Is there a configuration property that I am missing?
I appreciate the help.
You don't need all those annotations to make it working... I would recommend please remove those which are not added purposely by you..!!
To serve your static page on different path than the main context, here is a work-around..!!
Create another simple controller class like below..
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class Home {
#RequestMapping(path = "/")
public String getHome(){
return "redirect:/admin/ui/";
// make sure no space between colon (:) and endpoint name (/admin/ui)
}
#RequestMapping(path = "/admin/ui/" )
public String getAdminUi(){
return "/index.html";
// your index.html built by angular should be in resources/static folder
// if it is in resources/static/dist/index.html,
// change the return statement to "/dist/index.html"
}
}
And, notice here, I have marked the class as #Controller not the #RestController so if you mark it to #RestController or try to do the same in any existing #RestController you would not achieve it easily. So, it's no harm to create another class like above.
Benefit of this way is, it don't destroy your existing mappings.. also the context path is not changes, so no need to bother about your other endpoint paths. They all shall work as before.
Hope this helped!!

Java SpringMVC where to place the RepositoryRestMvcConfiguration (#Configuration) file

I have come across so many tutorials about configuration files for java SpringMVC projects and usually have the #Configuration at their class name but they never mention where exactly to place these files.
For example I have learnt that I might be able to change Spring Data Rest rest api by extending the RepositoryRestMvcConfiguration.
1) I want to be able to be able to have longer urls for the repositories so instead of having api/amazonproducts I could have api/amazon/products. Apparently this is impossible. I end up having;
#RepositoryRestResource(collectionResourceRel = "amazonproducts", path = "amazonproducts")
#CrossOrigin
public interface AmazonProductRepository extends PagingAndSortingRepository<AmazonProduct, Long> {
https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.html
All I have to do is for example create class like;
class CustomRestMvcConfiguration extends RepositoryRestMvcConfiguration {
#Override
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
resolver.setOneIndexedParameters(true);
return resolver;
}
}
I don't know in which file/folder to place this code. I am using Eclipse Java EE Oxygen. I have created a maven project and I am currently testing it on spring-boot
You can place #Configuration annotated classes in any package you want, as long as the package is included in the component scanning.
In Spring Boot, that by default means in the same package as the class with #SpringBootApplication, or any subpackage thereof, same as for all other classes managed by Spring.
Other than that, Spring doesn't care about package names.

Spring web application healthchecks

I'm deploying Spring based web applications on Amazon's Beanstalk platform, and they give me the option of setting a "healthcheck" URL path for my application.
The idea is that their platform will do a request against that URL, after the deployment, to see if the application is successfully started. So, if the request results in an HTTP 200, the application is probably fine. But if it results in an HTTP 500 or something else, the platform knows there's a problem with the application.
So, I wish I could develop some kind of servlet that would check if the Spring Application Context was successfully initialised, or not, to give an appropriate HTTP response code to the platform.
Has anybody attempted something like this? For similar purposes?
I'm wondering if Spring already provides some elegant solution for this.
I'd suggest using health checks functionality from Metrics. You could set up a number of classes that extend HealthCheck class and implement check() method. These health check implementations would be Spring managed beans themselves and could autowire Spring beans and validate them. Then configure HealthCheckServlet to monitor application state. Also check metrics-spring project. It will make Spring and Metrics integration simpler.
If you are using Java Spring configuration you might have a Metrics config like this that extends MetricsConfigurerAdapter from metrics-spring
#Configuration
#EnableMetrics
public class MetricsConfig extends MetricsConfigurerAdapter { }
And then #Import(value = {MetricsConfig.class}) to your Spring config.
You also need and implementation of ServletContextListener to wire up HealthCheckServlet and Spring. This HealthCheckContextListener should be added to your web.xml
public class HealthCheckContextListener extends
HealthCheckServlet.ContextListener implements ServletContextListener {
private WebApplicationContext context;
public HealthCheckContextListener(WebApplicationContext context) {
this.context = context;
}
public HealthCheckContextListener() {}
#Override
public void contextInitialized(ServletContextEvent event) {
this.context = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext());
event.getServletContext().setAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY,
context.getBean(HealthCheckRegistry.class));
}
#Override
protected HealthCheckRegistry getHealthCheckRegistry() {
return (HealthCheckRegistry) context.getBean(HealthCheckRegistry.class);
}
}
The simplest thing you can do is this:
#Controller
class HealthCheckController {
#ResponseStatus(OK)
#RequestMapping(value = "/ping", method = HEAD) {
public void ping() {
}
}
Extendable to also test particular beans, DataSources etc.
You should consider what constitutes a healthy app for you (e.g., servlet tier? JMS queues? FTP servers? etc.) and have your health check verify those services' availability. Obviously the health check is going to run frequently, so you don't want to initiate expensive operations over and over again.
Spring Boot is a new project that aims to simplify Spring development by favoring convention instead of configuration. They have implemented a "health check" feature that you can add to a project via an Actuator add-in module.
Here's a reference to their Health Check implementation -- it uses a controller class to return "ok" and, if there is a data source, attempts to run a query to confirm that the database is accessible (something like "SELECT .. from dual" in Oracle syntax).
This can easily be done in the spring boot framework. By adding below dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
You can check the service by hitting the below URL.
localhost:serverPort/actuator/health

Can Spring Security use #PreAuthorize on Spring controllers methods?

Can Spring Security use #PreAuthorize on Spring controllers methods?
Yes, it works fine.
You need <security:global-method-security pre-post-annotations="enabled" /> in ...-servlet.xml. It also requires CGLIB proxies, so either your controllers shouldn't have interfaces, or you should use proxy-target-class = true.
See Spring Security FAQ (emphasis mine).
In a Spring web application, the application context which holds the
Spring MVC beans for the dispatcher servlet is often separate from the
main application context. It is often defined in a file called
myapp-servlet.xml, where “myapp” is the name assigned to the Spring
DispatcherServlet in web.xml. An application can have multiple
DispatcherServlets, each with its own isolated application context.
The beans in these “child” contexts are not visible to the rest of the
application. The “parent” application context is loaded by the
ContextLoaderListener you define in your web.xml and is visible to all
the child contexts. This parent context is usually where you define
your security configuration, including the
element). As a result any security constraints applied to methods in
these web beans will not be enforced, since the beans cannot be seen
from the DispatcherServlet context. You need to either move the
declaration to the web context or moved the
beans you want secured into the main application context.
Generally we would recommend applying method security at the service
layer rather than on individual web controllers.
If you apply pointcuts to service layer you only need to set <global-method-security> in your app's security context.
If you're using Spring 3.1, you can do some pretty cool stuff with this. Take a look at https://github.com/mohchi/spring-security-request-mapping. It's a sample project that integrates #PreAuthorize with Spring MVC's RequestMappingHandlerMapping so that you can do something like:
#RequestMapping("/")
#PreAuthorize("isAuthenticated()")
public String authenticatedHomePage() {
return "authenticatedHomePage";
}
#RequestMapping("/")
public String homePage() {
return "homePage";
}
A request for "/" will call authenticatedHomePage() if the user is authenticated. Otherwise it will call homePage().
It's over two years since this question was asked but because of problems I had today I'd rather discourage using #Secured, #PreAuthorize, etc. on #Controllers.
What didn't work for me was #Validated combined with #Secured controller:
#Controller
#Secured("ROLE_ADMIN")
public class AdministrationController {
// #InitBinder here...
#RequestMapping(value = "/administration/add-product", method = RequestMethod.POST)
public String addProductPost(#ModelAttribute("product") #Validated ProductDto product, BindingResult bindingResult) {
// ...
}
Validator simply does not fire (Spring MVC 4.1.2, Spring Security 3.2.5) and no checks are performed.
Similar problems are caused by CGLIB proxies used by Spring (when there is no interface implemented by a class, Spring creates CGLIB proxy; if the class implements any interface then JDK Proxy is generated - documentation, well explained here and here).
As mentioned in the answers that I linked above, is't better to use Spring Security annotations on service layer that usually implements interfaces (so JDK Proxies are used) as this does not lead to such problems.
If you want to secure web controllers, the better idea is to use <http> and <intercept-url /> that are bound to specific urls rather than methods in controllers and work pretty well. In my case:
<http use-expressions="true" disable-url-rewriting="true">
...
<intercept-url pattern="/administration/**" access="hasRole('ROLE_ADMIN')" />
</http>
There is already a reply regarding how to make it work by changing xml configuration; however, if you are working with code-based configuration, you can achieve the same by placing the following annotation over your #Configuration class:
#EnableGlobalMethodSecurity(prePostEnabled=true)
To Extend the answer provided by Andy, you can use:
#PreAuthorize("hasRole('foo')")
to check the specific role.
step1: add #EnableGlobalMethodSecurity(prePostEnabled = true) annotation in SecurityConfig class .
like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.....
}
step 2: you can add #PreAuthorize() annotation in controller or service or repository layer. in a method or class level.
for example:
#RestController
#PreAuthorize("isAuthenticated()")
public class WebController {
#PreAuthorize("permitAll()")
#GetMapping("/")
public String home() {
return "Welcome home!";
}
#GetMapping("/restricted")
public String restricted() {
return "restricted method";
}
}
or
#RestController
public class AdminController {
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/admin")
public String adminMethod() {
}
}
First you need to add this annotation in your WebSecurityConfig to enable #Pre and #Post annotations.
#EnableGlobalMethodSecurity(prePostEnabled = true)
You can also check roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
equivalent to
#PreAuthorize("hasRole('ROLE_ADMIN')")
You can also check multiple roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER') or ...")

Categories

Resources