i had lots of problems adding Secured annotations to my Controllers.
it turns out letting my Controller implement an InitializingBean was a bad idea.
public class MyController implements InitializingBean {
#Secured(value="ROLE_ADMIN")
#RequestMapping(method = RequestMethod.GET, value = "/{id}/edit")
public String getView(Model model, #PathVariable("id") long id) {
return "some view";
}
}
this failed with:
WARN PageNotFound:962 - No mapping
found for HTTP request with URI[...]
removing the #Secured Annotation would work, but obviously i didn't want to do that.
after lots of wasted time on the net i noticed the last difference beetween a working and a non working controller was that it implemented the InitializingBean Interface. And now this works like a charm:
public class MyController{
#Secured(value="ROLE_ADMIN")
#RequestMapping(method = RequestMethod.GET, value = "/{id}/edit")
public String getView(Model model, #PathVariable("id") long id) {
return "some view";
}
}
Can anyone help me understand that behaviour?
This happens because access to the annotations is lost when security aspect is applied using JDK dynamic proxy, which happens by default when advised bean implements any interfaces.
To solve this problem, you should tell Spring Security to apply target-class-based proxies only, using <global-method-security proxy-target-class = "true" ...> ... (<aop:config proxy-target-class = "true" /> works too).
More about AOP proxies here.
Related
I am using a custom code generator to transform a REST contract in to Java interfaces. Below find a sample of a generated resource Java interface.
#Generated(
date = "2018-01-30T11:56:25.156Z",
comments = "Specification filename: country.v1.json",
value = "GENERATOR"
)
#RequestMapping("/v1/countries")
public interface CountriesResource {
#RequestMapping(
method = RequestMethod.GET,
path = "/{id}",
produces = MediaType.APPLICATION_JSON_VALUE
)
#ResponseBody
LandGetResourceModel getEntity(#PathVariable("id") String id);
}
Additionally the generator creates an #RestController implementation a that interface for Spring to create a controller bean.
#RestController
public class CountriesResourceImpl implements CountriesResource {
#Override
public CountryGetResourceModel getEntity(String id) {
return new CountryGetResourceModel();
}
}
So far everything works fine. Spring creates the RestController bean and the HTTP calls are directed correctly to the corresponding Handler method (e.g. getEntity).
The problem is that for some reason Spring is not able to resolve path variables when they are defined on the interface. All calls containing a path variable are handled but the method parameter of any path variable is null. If I then add the PathVariable annotation to the implementation class Spring is able to resolve the corresponding value.
Is there a way to tell Spring to read the PathVariable from the interface method declaration like it does for the RequestMapping annotations?
Unfortunately, seems like it is a known issue. I've found the following opened Jira on the question. Some explanation can be found from this answer. Also, in the same question I took the previous answer I see this solution. However, I haven't tried it and it seems to be heavy.
Spring supports two different validation methods: Spring validation and JSR-303 bean validation. Both can be used by defining a Spring validator that delegates to other delegators including the bean validator. So far so good.
But when annotating methods to actually request validation, it's another story. I can annotate like this
#RequestMapping(value = "/object", method = RequestMethod.POST)
public #ResponseBody TestObject create(#Valid #RequestBody TestObject obj, BindingResult result) {
or like this
#RequestMapping(value = "/object", method = RequestMethod.POST)
public #ResponseBody TestObject create(#Validated #RequestBody TestObject obj, BindingResult result) {
Here, #Valid is javax.validation.Valid, and #Validated is org.springframework.validation.annotation.Validated. The docs for the latter say
Variant of JSR-303's Valid, supporting the specification of validation
groups. Designed for convenient use with Spring's JSR-303 support but
not JSR-303 specific.
which doesn't help much because it doesn't tell exactly how it's different. If at all. Both seem to be working pretty fine for me.
A more straight forward answer.
For those who still don't know what on earth is "validation group".
Usage for #Valid Validation
Controller:
#RequestMapping(value = "createAccount")
public String stepOne(#Valid Account account) {...}
Form object:
public class Account {
#NotBlank
private String username;
#Email
#NotBlank
private String email;
}
Usage for #Validated Validation Group
Source: http://blog.codeleak.pl/2014/08/validation-groups-in-spring-mvc.html
Controller:
#RequestMapping(value = "stepOne")
public String stepOne(#Validated(Account.ValidationStepOne.class) Account account) {...}
#RequestMapping(value = "stepTwo")
public String stepTwo(#Validated(Account.ValidationStepTwo.class) Account account) {...}
Form object:
public class Account {
#NotBlank(groups = {ValidationStepOne.class})
private String username;
#Email(groups = {ValidationStepOne.class})
#NotBlank(groups = {ValidationStepOne.class})
private String email;
#NotBlank(groups = {ValidationStepTwo.class})
#StrongPassword(groups = {ValidationStepTwo.class})
private String password;
#NotBlank(groups = {ValidationStepTwo.class})
private String confirmedPassword;
}
As you quoted from the documentation, #Validated was added to support "validation groups", i.e. group of fields in the validated bean. This can be used in multi step forms where you may validate name, email, etc.. in first step and then other fields in following step(s).
The reason why this wasn't added into #Valid annotation is because that it is standardized using the java community process (JSR-303), which takes time and Spring developers wanted to allow people to use this functionality sooner.
Go to this jira ticket to see how the annotation came into existence.
In the example code snippets of the question, #Valid and #Validated make no difference. But if the #RequestBody is annotated with a List object, or is a string value annotated by #RequestParam, the validation will not take effect.
We can use the #Validated's method-level validation capability to make it work. To achieve this, the key point is to place #Validated on the class. This may be another important difference between #Valid and #Validated in spring framework.
Refrence
Spring boot docs
Just for simplifying:
#Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class.
and
#Valid annotation on method parameters and fields to tell Spring that we want a method parameter or field to be validated.
besides above, you can only apply #Valid on a domain/field for nested validation, not with a #validated.
#Validated can be used for a class:
#Validated
public class Person {
#Size(min=3)
private String name;
...
Is it possible to create a Java annotations equivalent to a Spring MVC #RequestMapping with a predefined set of parameters?
For instance, something that allows me to simply use:
#PostJson("/input")
public Output myMethod(Input input) {
instead of:
#RequestMapping(value = "/input",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public Output myMethod(Input input) {
UPDATE:
Let me try to dig a little deeper. AFAIK, Spring seems to be able to handle meta-annotations when scanning for beans. For instance, if I create an annotation, lets say #MyComponent, and annotate it with #Component:
#Component
public #interface MyComponent {
String value() default "";
}
Spring seems able to find beans with #MyComponent and to recognize the parameters (value in this case) in #MyComponent as if they were from #Component
#MyComponent("MyBean")
public class SomeClass {
I've tryed similar tactic with #RequestMapping
#RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public #interface PostJson {
String value() default "";
}
The fixed parameters (method an produces) seems to be correct, nonetheless, the variable parameter (value) is ignored.
I am hoping that this is not a #Component specific feature and that I could use it with #RequestMapping.
Not easily, no. The #RequestMapping annotation is tied to RequestMappingHandlerMapping and RequestMappingHandlerAdapter. These are two elements of the MVC stack registered by default when you provide <mvc:annotation-driven /> or #EnabledWebMvc. They only scan for #Controller and #RequestMapping, nothing else.
To make your own annotation work, you'd have to override (and register) these two or create new ones from scratch which provide the scanning and handling of handler methods. You can get some inspiration from those classes and other HandlerMapping implementations, but it really isn't a simple task.
You might, alternatively, want to look into Java Restful Web Services which can integrate quite well with Spring (not necessarily Spring MVC though). It can provide some less bloated mapping annotations when you know exactly what you want.
While this isn't currently supported out of the box but will be (thanks for creating https://jira.spring.io/browse/SPR-12296!), it isn't hard to do. If you look in RequestMappingHandlerMapping the protected method getMappingForMethod accepts a method and returns a RequestMappingInfo populated with information from the type + method-level #RequestMapping annotations. You can however populate this RequestMappingInfo from anything, e.g. your own annotation, or some other source (external routes configuration). Just follow the example of the code there.
I'm trying to implement a #Restricted annotation, to secure controller methods in a way that users can only access them, when they are logged in and have a certain role. I'm on Tomcat 7 using JSF and CDI, so no EJB. The interceptor gets called as long as the annotation interface does not specify any parameters. As soon as I add a #Nonbinding Role value() default Role.ADMIN; parameter, neither the interceptor nor the controller method execute. No errors or exceptions either. Here is my code, I really don't know what's wrong with it:
Annotation:
#InterceptorBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface Restricted {
#Nonbinding Role value() default Role.ADMIN; // ###
}
Interceptor:
#Interceptor
#Restricted
public class RoleBasedRestrictingInterceptor implements Serializable {
#Inject
ISecurityManager security;
#AroundInvoke
public Object intercept(final InvocationContext ctx) throws Exception {
final Restricted annotation = ctx.getClass().getAnnotation(Restricted.class);
log.info("Intercepted, required role is: {}", annotation.value()); // ###
log.info("User is logged in: {}", security.isLoggedIn());
return ctx.proceed();
}
}
Controller:
#Named("manageUsers")
#SessionScoped
public class ManageUsersBacking extends implements Serializable {
#Restricted(Role.ADMIN) // ###
public void testRestricted() {
log.info("testRestricted()");
}
}
The ### occurrences mark what has to be changed or removed to make it work again. The interceptor is properly defined in WEB-INF/beans.xml, since it works without the role parameter in my annotation.
16:04:33.772 [http-apr-8080-exec-11] INFO c.m.s.RoleBasedRestrictingInterceptor - User is logged in: true
16:04:33.772 [http-apr-8080-exec-11] INFO c.m.c.admin.ManageUsersBacking - testRestricted()
Today I revisited this particular problem and noticed it had nothing to do with CDI:
ctx.getClass().getAnnotation(Restricted.class)
Obviously, there is no class level annotation in my example. So getAnnotation() returns null. Instead I should have used the following:
ctx.getMethod().getAnnotation(Restricted.class)
Though I don't know why there where no exceptions whatsoever. Maybe some other things were going on, that I can no longer reproduce because I migrated my application to TomEE.
if you switch to TomEE you'll don't need to depend (maven) on implementations, just api (use org.apache.openejb:javaee-api:6.0-4 with a provided scope
It sounds like you have things setup correct (beans.xml and interceptor). Which CDI implementation are you using? If you're using Tomcat have you looked at using TomEE?
I have some serious doubts regarding scope bean dependencies because of lack of spring knowledge.
I have read reference manual at 3.5.4.5 Scoped beans as dependencies and have implemented sucessfully an example about it.
However before going further more, I wanted to share my concerns.
Let me to share my use case and little implementation details
For each user request I would like to create a city for per user.
#Configuration
public class CityFactory{
#Bean(name = {"currentCity" , "loggedInCity"})
#Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.TARGET_CLASS)
#Autowired
public CityBean getCityBean(HttpServletRequest request) {
return CityUtil.findCityWithHostName(request.getServerName());
}
For each request I want to inject this city into a singleton scoped controller which is default scope for controller.
#RequestMapping("/demo")
#Controller
public class DemoController {
#Autowired
CityBean city;
#RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
public ModelAndView helloWorld(#PathVariable("name") String name, Model model) {
Map<String, Object> myModel = new HashMap<String, Object>();
model.addAttribute("hello", name);
model.addAttribute("test", "test in " + city.getDomainName() + " !!! ");
return new ModelAndView("v3/test", "m", model);
}
}
My questions:
1) Is there any race condition? I am afraid of context switches which will destroy my application in a multi request environment.
2) I am aware of another solution which creating a controller per request but it is more error prone than current solution. Because another developer can forget scoping controllers to make request.
How can I make controllers globally request scope? Just because of being little curious.
Thanks...
No race conditions - each request has its own thread
But I think there's an easier way to do what you want. You can have your CityBean:
#Service
public class CityBean {
public String getDomainName(String serverName) {
// obtain the name based on the server name
}
}
And in your controller:
#Autowired CityBean bean
pass HttpServletRequest as argument to the method, and call cityBean.getDomainName(request.getServerName());
(If you use some ORM, perhaps you'll have a City entity, which you can fetch and pass around, just beware of lazy collections)
There are no race conditions here.
That's the point of scoped proxies - the instance of CityBean injected into DemoController is a proxy which delegates calls of its method to the actual request-bound instance of CityBean, so that each request works with its own CityBean.
I agree that you shouldn't make the controller itself request-scoped - it would be confusing for other people since it's not a typical approach in Spring MVC applications.
You can also follow the approach suggest by Bozho and get rid of request-scoped bean at all, though that approach has a drawback since it requires you to add extra argument to your controller methods.