I am trying to look for a way to extend a controller such that I can use the existing request mapping but with a different implementation/method tied to it.
Say for example, below is a code snippet from AdminBasicEntityController in BLC where viewAddEntityForm method is tied to /add request mapping. Now I want to have my own logic of showing entityForm(Say Product entity) using /add request mapping. Is it possible?
#Controller("blAdminBasicEntityController")
#RequestMapping("/{sectionKey:.+}")
public class AdminBasicEntityController extends AdminAbstractController {
#RequestMapping(value = "", method = RequestMethod.GET)
public String viewEntityList(HttpServletRequest request, HttpServletResponse response, Model model,
#PathVariable Map<String, String> pathVars,
#RequestParam MultiValueMap<String, String> requestParams) throws Exception {
// default implementation
}
#RequestMapping(value = "/add", method = RequestMethod.GET)
public String viewAddEntityForm(HttpServletRequest request, HttpServletResponse response, Model model,
#PathVariable Map<String, String> pathVars,
#RequestParam(defaultValue = "") String entityType) throws Exception {
// default implementation
}
}
Also I found below mentioned information in the documentation of AdminBasicEntityController, so does it mean I can have controller for specific entity. If yes, how?
The default implementation of the {#link #BroadleafAdminAbstractEntityController}. This delegates every call to super and does not provide any custom-tailored functionality. It is
responsible for rendering the admin for every entity that is not
explicitly customized by its own controller
#RequestMapping("/{sectionKey:.+}") uses the generic path variable sectionKey, making the controller handle any requests that are not explicitly mapped. For example,
/product
/product/add
/category
/category/add
/store
/store/add
may all be hitting this controller if these URLs have not been explicitly mapped to their own controllers.
To handle a specific URL yourself, you could do:
#Controller
#RequestMapping("/product")
public class ProductAdminController {
#RequestMapping("/add")
public String viewAddEntityForm(...) { ... }
}
Now, the URL /product/add will be routed to this custom controller while all others will continue to get routed to the generic controller.
Related
I have a standard #RestController method. I introduced a parameter-object (MyMapQuery) in order to avoid large number of method arguments:
#RestController
public class MyController {
#RequestMapping(value = "/api/search")
#ResponseBody
public SearchResponse search(MyMapQuery query) {
[...]
}
MyMapQuery is a standard java bean with setters and getters, so when I pass multiple url parameters http://.../api/search?west=1&east=2&north=20&south=0, they are correctly filled in.
How do I implement required validation on some of the url parameters?
Spring automatically responds with 400(bad request), when parameters are mapped like below, but does no validation in case of MyMapQuery.
public SearchResponse search(#RequestParam BigDecimal east, #RequestParam BigDecimal west, ...) {
Just use
public SearchResponse search(#Valid MyMapQuery query) {
and add the necessary bean-validation annotations to the fields of MyMapQuery.
I am using spring boot, and I have enabled the global method security in WebSecurityConfigurerAdapter by
#EnableGlobalMethodSecurity(prePostEnabled = true, order = Ordered.HIGHEST_PRECEDENCE)
And Below is my controller code
#PreAuthorize("hasAnyRole('admin') or principal.id == id")
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public User updateUser(#PathVariable("id") String id, #Valid #RequestBody UserDto userDto)
{ ....}
However, when a non-admin user try to do a PUT request, the JSR303 validator will kick in before #PreAuthorize.
For example, non-admin user ended up getting something like "first name is required" instead of "access denied". But after user supplied the first name variable to pass the validator, access denied was returned.
Does anyone know how to enforce the #PreAuthorize get checked before #Valid or #Validated?
And I have to use this kind of method-level authorization instead of url-based authorization in order to perform some complex rule checking.
I had the same issue and I found this post. The comment of M. Deinum helps me to understand what was going wrong
Here is what I did :
The public method has the #PreAuthorize and do the check
There is NO #Valid on the #RequestBody parameter
I create a second method, private, where I do the DTO validation. Using the #Valid annotation
The public methods delegates the call to the private one. The private method is called only is the public method is authorized
Example :
#RequestMapping(method = RequestMethod.POST)
#PreAuthorize("hasRole('MY_ROLE')")
public ResponseEntity createNewMessage(#RequestBody CreateMessageDTO createMessageDTO) {
// The user is authorized
return createNewMessageWithValidation(createMessageDTO);
}
private ResponseEntity createNewMessageWithValidation(#Valid CreateMessageDTO createMessageDTO) {
// The DTO is valid
return ...
}
For the same scenario, I have found reccomendations to implement security via spring filters.
Here is similar post : How to check security acess (#Secured or #PreAuthorize) before validation (#Valid) in my Controller?
Also, maybe a different approach - try using validation via registering a custom validator in an #InitBinder (thus skip the #valid annotation).
To access principal object in filter class:
SecurityContextImpl sci = (SecurityContextImpl)
session().getAttribute("SPRING_SECURITY_CONTEXT");
if (sci != null) {
UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();
}
In this case /{id} is a path param in the URL. To access path params in filter or interceptor class:
String[] requestMappingParams = ((HandlerMethod)handler).getMethodAnnotation(RequestMapping.class).params()
for (String value : requestMappingParams) {.
Use WebSecurityConfigurerAdapter.configure(HttpSecurity http) instead of #PreAuthorize
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers( "/path/**").hasRole("admin");
}
}
In Broadleaf Commerce framework, I am looking for a way to pull domain entity objects like product, admin user from entity form that we receice as model attribute in CRUD methods of EntityController classes like AdminProductController. Is it possible? If yes, how?
Below is a sample code from AdminProductController class.
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String addEntity(HttpServletRequest request, HttpServletResponse response, Model model,
#PathVariable Map<String, String> pathVars,
#ModelAttribute(value="entityForm") EntityForm entityForm, BindingResult result) throws Exception {
//some code
}
Using a strictly annotation-based/Java-only Spring MVC 3.2.2 configuration, I'm trying to create a controller method with a custom class (I'll call it Context) as a parameter. I want to have Context constructed with knowledge of the current HttpServletRequest and then passed along to the controller method. In essence, I want to create my own custom wrapper around the request object before it's sent to the controller. e.g. I want to accomplish this:
#Controller
#RequestMapping(value = "/")
public class MainController {
#RequestMapping(value = "/")
public #ResponseBody
String process(HttpServletRequest request) {
Context context = new Context(request);
...
}
}
automatically like this:
#Controller
#RequestMapping(value = "/")
public class MainController {
#RequestMapping(value = "/")
public #ResponseBody
String process(Context context) {
...
}
}
Is this possible? I looked into implementing a HandlerMethodArgumentResolver as a #Bean in my WebMvcConfigurerAdapter but I don't think that's the correct route to take. I've tried adding #AutoWired to Context (as a #Bean) to no avail as well. I imagine there's WebMvcConfigurerAdapter or possibly in a AbstractAnnotationConfigDispatcherServletInitializer?
HandlerMethodArgumentResolver and WebMvcConfigurerAdapter is certainly a right way to achieve your goal.
In order to register custom argument resolver you need to make your #Configuration extend WebMvcConfigurerAdapter and override its addArgumentResolver() method.
Currently I am using request.setAttribute() and request.getAttribute() as a means to pass an object from a handler interceptor to a controller method. I don't view this as an ideal technique, because it requires that I take HttpServletRequest as an argument to my controller methods. Spring does a good job hiding the request object from controllers, so I would not need it except for this purpose.
I tried using the #RequestParam annotation with the name I set in setAttribute(), but of course that did not work because request attributes are not request params. To my knowledge, there is no #RequestAttribute annotation to use for attributes.
My question is, is there some better way to hand off objects from interceptors to controller methods without resorting to setting them as attributes on the request object?
Use the interceptor prehandle method and session like this:
Interceptor:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HttpSession session = request.getSession();
String attribute = "attribute";
session.setAttribute("attributeToPass", attribute);
return true;
}
Controller:
#RequestMapping(method = RequestMethod.GET)
public String get(HttpServletRequest request) {
String attribute = (String)request.getSession().getAttribute("attribteToPass");
return attribute;
}
Just to save time for those visiting this page: since Spring 4.3 #RequestAttribute annotation is a part of Spring MVC, so there is no need to create your own #RequestAttribute annotation.
An example using #RequestAttribute:
Interceptor
#Component
public class ExampleRequestInterceptor
implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Logic to verify handlers, you custom logic, etc.
// Just for illustration, I'm adding a `List<String>` here, but
// the variable type doesn't matter.
List<String> yourAttribute = // Define your attribute variable
request.setAttribute("yourAttribute", yourAttribute);
return true;
}
}
Controller
public ResponseEntity<?> myControllerMethod(#RequestParam Map<String, String> requestParams, #RequestAttribute List<String> yourAttribute) {
// `yourAttribute` will be defined here.
}