Understanding "globalValidator" in Spring MVC - java

I have custom validator and I register it in my controller
#Controller
public class MyController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}
#RequestMapping("/foo", method=RequestMethod.POST)
public void processFoo(#Valid Foo foo) { ... }
}
but I want to register in other controllers also,so to be able just to write #Valid and the Foo object to be validated. From what I see I understand that I can use #ControllerAdviced class which to register the validator on every controller, or to use
<mvc:annotation-driven validator="globalValidator"/>
But how to register my validator, how Spring understand which Validator I want to make global one? Scans for every implementing Validator class? Can I do it with xml configuration? How to use this approach?
I do not understand the Spring's description:
The alternative is to call setValidator(Validator) on the global
WebBindingInitializer. This approach allows you to configure a
Validator instance across all annotated controllers. This can be
achieved by using the SpringMVC namespace:
xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xss
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:annotation-driven validator="globalValidator"/>

The documentation is quite clear on the Validation section:
In Spring MVC you can configure it for use as a global Validator
instance, to be used whenever an #Valid or #Validated controller
method argument is encountered, and/or as a local Validator within a
controller through an #InitBinder method. Global and local validator
instances can be combined to provide composite validation
If I understand correctly in your example the FooValidator you want to use it upon every validation as global Validator so define it as a bean and inject it as you show directly in the mvc:annotation-driven XML entry as you are showing already.
On top of that per-Controller you can have custom (applied on top only on that Controller-responsible forms) via the #InitBinder annotation.
As a side note, in your #RequestMapping method receiving the POST request where your #Valid parameter is: You can have a BindingResult entry right after that to take decisions on routes etc. In your example:
#RequestMapping("/foo", method=RequestMethod.POST)
public String processFoo(#Valid Foo foo, BindingResult result) {
if(result.hasErrors()) {
return "go/that/way";
}
//..
}

Related

Testing authentication on REST endpoints

I could use some help testing the authentication (using spring authentication) of my spring REST endpoints.
I have defined some endpoints requiring authentication using the following annotation #PreAuthorize("isAuthenticated()").
This seems to be working, because when I start a webserver and go to the URL I'm asked to authenticate, after which I get the proper results.
I run into trouble with my tests.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestContext.class)
#WebAppConfiguration
public class ControllerTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
public void testGetMethod() throws Exception {
mvc
.perform(get("/a-valid-url")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isUnauthorized());
}
}
The TestContext class contains some autowired classes in the controller which are mocked, like this:
#Configuration
public class TestContext {
#Bean
public ClassToMock autowiredClass() {
return Mockito.mock(ClassToMock.class);
}
}
When run like this I get the following error:
java.lang.IllegalStateException: springSecurityFilterChain cannot be null. Ensure a Bean with the name springSecurityFilterChain implementing Filter is present or inject the Filter to be used.
The only solution I have found thus far to get everything running is by adding a springSecurityFilterChain bean in the TestContext class, like this:
#Bean
public Filter springSecurityFilterChain() {
return Mockito.mock(Filter.class);
}
However then I always get a Status = 200, even when I use an URL that is not defined in the controller.
If anyone could point me in the right direction it would be much appreciated!
Edit: Some of the configuration of the project.
web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The project has another project as a dependency which contains the TokenContext and AuthenticationStatelessContext configuration XML files.
TokenContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd" >
<context:component-scan base-package="com.example.auth.service" resource-pattern="TokenServiceImpl.class"/>
<context:component-scan base-package="com.example.auth.service" resource-pattern="ActorRolServiceImpl.class"/>
</beans>
The AuthenticationStatelessContext.xml defines the following spring beans: DefaultMethodSecurityExpressionHandler, PreAuthenticatedAuthenticationProvider and UserDetailsByNameServiceWrapper.
Update:
Created a class named TestContext2:
#Configuration
#ImportResource({
"classpath:**/AuthenticationStatelessContext.xml",
"classpath:**/TokenContext.xml"})
public class TestContext2 {
}
And added that class to the test
#ContextConfiguration(classes = {TestContext.class, TestContext2.class})
This because spring doesn't allow loading of classes and xml files at the same time in the #ContextConfiguration annotation.
When I try to run the test I still get the following error:
java.lang.IllegalStateException: springSecurityFilterChain cannot be null.
PS: The used springframework version is 4.1.3 and the spring-security version is 4.0.4.
It is wrong to mock the Filter interface to inject a springSecurityFilterChain bean.
What you want is to inject a filter chain with atleast one authentication filter, e.g. UsernamePasswordAuthenticationFilter
CasAuthenticationFilter
BasicAuthenticationFilter.
Read this spring security filter chain guide to understand which filters to inject for your purpose.
Right now mockito is setting a filter on the springSecurityFilterChain that allows all urls and does not provide the intended filtering behaviour.
The fix could be as easy as including a referrence to the correct app context along with TestContext on the Test class, depends on how you have defined the context for the rest of the application. I suspect a filter chain bean may be available for auto wiring as well.
Update:
Tell spring to load the TokenContext and AuthenticationStatelessContext here:
#ContextConfiguration(classes = TestContext.class)
Remove any code to define security filter chain bean in TestContext

How to create a master controller in Spring MVC

We have a case where a controller must always be executed in every request to set some default values in the model next we execute the required controller. How to set a master controller to be always executed including other controller in the same request?
You can make a base controller class, with a #ModelAttribute annotated method which getscalled on every request, before an actual handler method, e.g
#ModelAttribute
public void everyRequest(WebRequest request, Model model) {
model.addAttribute("default", true);
}
all thats left is to extend this base controller class from your actual controllers
If you're using Spring 4.x, a better approach is to use a #ControllerAdvice which assist all, or a selected group of components and can be used for either adding the model attribute, apply common init binding or error handling. An example
#ControllerAdvice
class Advice {
#ModelAttribute
public void everyRequest(Model model) {
model.addAttribute("default", true);
}
}
Use Spring HandlerInterceptor #preHandle, it provides you access to the Handler which may be useful and also provides you with the power to exclude calling of some of the controllers.
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="com.test.yourInterceptorClass" />
</mvc:interceptor>

How to set base url for rest in spring boot?

I'm trying to to mix mvc and rest in a single spring boot project.
I want to set base path for all rest controllers (eg. example.com/api)
in a single place (I don't want annotate each controller with #RequestMapping('api/products'), instead, just #RequestMapping('/products').
Mvc controllers should be accessible by example.com/whatever
Is it possible?
(I don't use spring data rest, just spring mvc)
With Spring Boot 1.2+ (<2.0) all it takes is a single property in application.properties:
spring.data.rest.basePath=/api
ref link : https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.changing-base-uri
For 2.x, use
server.servlet.context-path=/api
A bit late but the same question brought me here before reaching the answer so I post it here.
Create (if you still don't have it) an application.properties and add
server.contextPath=/api
So in the previous example if you have a RestController with #RequestMapping("/test") you will access it like localhost:8080/api/test/{your_rest_method}
question source: how do i choose the url for my spring boot webapp
For spring boot framework version 2.0.4.RELEASE+. Add this line to application.properties
server.servlet.context-path=/api
Try using a PathMatchConfigurer (Spring Boot 2.x):
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
I couldn't believe how complicate the answer to this seemingly simple question is. Here are some references:
Spring JIRA Ticket
Another SO question
Yet another SO question
Very nice GitRepository that showcases the problem
There are many differnt things to consider:
By settingserver.context-path=/api in application.properties you can configure a prefix for everything.(Its server.context-path not server.contextPath !)
Spring Data controllers annotated with #RepositoryRestController that expose a repository as rest endpoint will use the environment variable spring.data.rest.base-path in application.properties. But plain #RestController won't take this into account. According to the spring data rest documentation there is an annotation #BasePathAwareController that you can use for that. But I do have problems in connection with Spring-security when I try to secure such a controller. It is not found anymore.
Another workaround is a simple trick. You cannot prefix a static String in an annotation, but you can use expressions like this:
#RestController
public class PingController {
/**
* Simple is alive test
* #return <pre>{"Hello":"World"}</pre>
*/
#RequestMapping("${spring.data.rest.base-path}/_ping")
public String isAlive() {
return "{\"Hello\":\"World\"}";
}
}
Since this is the first google hit for the problem and I assume more people will search for this. There is a new option since Spring Boot '1.4.0'.
It is now possible to define a custom RequestMappingHandlerMapping that allows to define a different path for classes annotated with #RestController
A different version with custom annotations that combines #RestController with #RequestMapping can be found at this blog post
#Configuration
public class WebConfig {
#Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
private final static String API_BASE_PATH = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
if (AnnotationUtils.findAnnotation(beanType, RestController.class) != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
};
}
};
}
}
You can create a custom annotation for your controllers:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#RestController
#RequestMapping("/test")
public #interface MyRestController {
}
Use it instead of the usual #RestController on your controller classes and annotate methods with #RequestMapping.
Just tested - works in Spring 4.2!
For Boot 2.0.0+ this works for me: server.servlet.context-path = /api
I found a clean solution, which affects only rest controllers.
#SpringBootApplication
public class WebApp extends SpringBootServletInitializer {
#Autowired
private ApplicationContext context;
#Bean
public ServletRegistrationBean restApi() {
XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
applicationContext.setParent(context);
applicationContext.setConfigLocation("classpath:/META-INF/rest.xml");
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/rest/*");
servletRegistrationBean.setName("restApi");
return servletRegistrationBean;
}
static public void main(String[] args) throws Exception {
SpringApplication.run(WebApp.class,args);
}
}
Spring boot will register two dispatcher servlets - default dispatcherServlet for controllers, and restApi dispatcher for #RestControllers defined in rest.xml:
2016-06-07 09:06:16.205 INFO 17270 --- [ main] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'restApi' to [/rest/*]
2016-06-07 09:06:16.206 INFO 17270 --- [ main] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
The example rest.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<context:component-scan base-package="org.example.web.rest"/>
<mvc:annotation-driven/>
<!-- Configure to plugin JSON as request and response in method handler -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonMessageConverter"/>
</list>
</property>
</bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</beans>
But, you're not limited to:
use XmlWebApplicationContext, you may use any else context type available, ie. AnnotationConfigWebApplicationContext, GenericWebApplicationContext, GroovyWebApplicationContext, ...
define jsonMessageConverter, messageConverters beans in rest context, they may be defined in parent context
I did some research on the differences of spring properties mentioned in this thread. Here are my findings if anybody is wondering.
spring.data.rest.basePath Property
spring.data.rest.basePath=/api
This property is specifically for Spring Data Rest projects. It won't work in a usual Spring MVC projects.
To change the context path in MVC projects, you can use those two properties mentioned below. Let me mention the differences too.
server.servlet.context-path Property
server.servlet.context-path=/api
This one sets the context path on your web servelet. This property perfectly works fine in both spring mvc and spring data rest projects. But, the differnce is the request url will be filter out before reaching spring interceptors. So it will respond with HTML on bad request. Not Spring's or your own custom JSON response (in #ResponseBodyAdvice annotated class) defined. To overcome that, you should use this property below.
spring.mvc.servlet.path Property
spring.mvc.servlet.path=/api
This will filter the request URL in spring mvc interceptors and will respond default/your custom JSON response if you invoke a bad request.
Conclusion:
So as the OP's question, I would suggest that he should use spring.mvc.servlet.path to change the context path.
I might be a bit late, BUT... I believe it is the best solution. Set it up in your application.yml (or similar config file):
spring:
data:
rest:
basePath: /api
As I can remember that's it - all of your repositories will be exposed beneath this URI.
You can create a base class with #RequestMapping("rest") annotations and extend all you other classes with this base class.
#RequestMapping("rest")
public abstract class BaseController {}
Now all classes that extend this base class will be accessible at rest/**.
With spring-boot 2.x you can configure in application.properties:
spring.mvc.servlet.path=/api
For those who use YAML configuration(application.yaml).
Note: this works only for Spring Boot 2.x.x
server:
servlet:
contextPath: /api
If you are still using Spring Boot 1.x
server:
contextPath: /api
server.servlet.context-path=/api would be the solution I guess. I had the same issue and this got me solved. I used server.context-path. However, that seemed to be deprecated and I found that server.servlet.context-path solves the issue now. Another workaround I found was adding a base tag to my front end (H5) pages. I hope this helps someone out there.
Cheers
You can create a custom annotation for your controllers:
Use it instead of the usual #RestController on your controller classes and annotate methods with #RequestMapping.
Works fine in Spring 4.2!
For Spring WebFlux the approach is similar to Harald's, but with the obvious WebFlux configuration set up:
#Configuration
public class WebFluxConfig implements WebFluxConfigurer {
#Override
public void configurePathMatching(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
And for Kotlin it's:
#Configuration
class WebFluxConfig : WebFluxConfigurer {
override fun configurePathMatching(configurer: PathMatchConfigurer) {
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
}
This solution applies if:
You want to prefix RestController but not Controller.
You are not using Spring Data Rest.
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new ApiAwareRequestMappingHandlerMapping();
}
private static class ApiAwareRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private static final String API_PATH_PREFIX = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
if (AnnotationUtils.findAnnotation(beanType, RestController.class) != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_PATH_PREFIX)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern, mapping.getMethodsCondition(),
mapping.getParamsCondition(), mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
}
}
This is similar to the solution posted by mh-dev, but I think this is a little cleaner and this should be supported on any version of Spring Boot 1.4.0+, including 2.0.0+.
Per Spring Data REST docs, if using application.properties, use this property to set your base path:
spring.data.rest.basePath=/api
But note that Spring uses relaxed binding, so this variation can be used:
spring.data.rest.base-path=/api
... or this one if you prefer:
spring.data.rest.base_path=/api
If using application.yml, you would use colons for key separators:
spring:
data:
rest:
basePath: /api
(For reference, a related ticket was created in March 2018 to clarify the docs.)
worked server.contextPath=/path

How Can I create Spring Bean outside of Spring Application Context

I'm developing some kind of plugin which should be called by external java app.
my Plugin is using Spring and of cause I tried to simplify my as I can:
Let's consider that this is 3d party app and it's calling my plugin in its main function.
public class ThirdPartyClass {
public static void main(String[] args) {
GeneralPlugin plugin = new MyPlugin();
plugin.init();
//calling ext. plugin functionality.
plugin.method1();
}
}
Now this is my plugin
package com.vanilla.spring;
#Component
public class MyPlugin implements GeneralPlugin{
#Autowired
Dao mydao;
public void init(){
//some initiation logic goes here...
}
public void method1(){
dao.delete();
}
}
Now my Dao
package com.vanilla.spring;
Component(value="dao")
public class MyDao {
public void delete(){
//SOME DATABASE LOGIC GOES HERE!!!
}
}
now my XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
<context:annotation-config />
<context:component-scan base-package="com.vanilla.spring"></context:component-scan>
</beans>
My problem is that my dao is null and I'm getting NullPointerException when accessing dao object.
I
m believe it happens because I'm initiation bean out of Application context and as the result my Autowiring is not working.
Is there any other way to make autowiring work?
"Spring beans" are just that: Java beans. They have no intrinsic abilities other than those given to them by inheritance or object instantiation.
The Spring Application Context is responsible for creating the bean and "wiring" it, which is the process of creating other beans in the context and calling the bean's setters (and constructors) with the results to configure them. To do this is uses the XML configuration file and annotations to decide what to create and where to put it.
If you aren't going to use an actual Application Context, then you have to do all of that work yourself, manually. That is, create the DAO with the proper data source, create the plugin bean, and set the DAO on the plugin bean.
In this specific example, since the 3rd party application controls the instantiation of your plugin bean, you will likely have to either a) create the DAO in the plugin constructor (which is what you're using Spring to avoid in the first place), or b) create an Application Context in the plugin constructor and reference the beans the plugin needs by querying the context. This isn't quite as useful as letting the context do everything, but at least you don't have to configure the rest of the beans your application uses manually (with user names, connection URLs, etc).
If you go the second route you would then need the Spring configuration file somewhere in the classpath or somehow otherwise able to be referenced by the plugin bean.

spring initBinder and webbindinginitializer example

I read few books on spring2.5 on these topic, but still cannot grab the concepts when to use #initBinder. can anyone share any reference or explain in what situation i can use this on web application? How propertyEditor relate to it?
Well I can't really put it any better than the books, but if your controller has any public methods annotated with #InitBinder, then these methods will be called by the container just before each request is processed, passing in the WebDataBinder being used by the framework.
The most common reason to do this is when you want to customise the way that Spring tries to bind request parameters on to your model, for example if your model has custom datatypes that Spring can't handle out of the box. You register your PropertyEditors against the WebDataBinder. A trivial example would be if you use the JodaTime library in your model, and you want to bind timestamp strings on to a Joda DateTime object.
With Spring 2.0, you use to have to override the protected initBinder() method from the controller superclass, but Spring 2.5 removes the need to do that, you can just use annotations now.
Another reason beside what skaffman mentioned, would be to set a custom validator on your WebDataBinder. What I will usually do is use JSR-303 bean validation, and then bind a validator that provides additional validation not provided by JSR-303.
Inside your controller:
#InitBinder
protected void initBinder(WebDataBinder webDataBinder) {
Validator validator = webDataBinder.getValidator();
webDataBinder.setValidator(new UserFormValidator(validator));
}
What I'm doing is taking in the bean validator, calling that inside my custom validator, and then calling my custom validations. Something like this:
public class UserFormValidator implements Validator {
private Validator validator;
public AuthUserFormValidator(Validator validator) {
this.validator = validator;
}
#Override
public boolean supports(Class<?> clazz) {
return UserForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
// Run the bean validation...
validator.validate(target, errors);
// Do your custom validation on userForm here...
UserForm userForm = (UserForm) target;
// Validation on userForm...
}
}
It require Spring 2.5.1+
see https://jira.springsource.org/browse/SPR-4182

Categories