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.
}
Related
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.
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");
}
}
I am adding rate-limiting to a restful webservice using Spring MVC 4.1.
I created a #RateLimited annotation that I can apply to controller methods. A Spring AOP aspect intercepts calls to these methods and throws an exception if there have been too many requests:
#Aspect
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
#Autowired
private RateLimitService rateLimitService;
#Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& #annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
This all works perfectly, except when the #RateLimited controller method has parameters marked as #Valid, e.g.:
#RateLimited
#RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
#Valid #RequestBody CreateAccountRequestDto dto) {
...
}
The problem: if validation fails, the validator throws MethodArgumentNotValidException, which is handled by an #ExceptionHandler, which returns an error response to the client, never triggering my #Before and therefore bypassing the rate-limiting.
How can I intercept a web request like this in a way that takes precedence over parameter validation?
I've thought of using Spring Interceptors or plain servlet Filters, but they are mapped by simple url-patterns and I need to differentiate by GET/POST/PUT/etc.
I eventually gave up on trying to find an AOP solution and created a Spring Interceptor instead. The interceptor preHandles all requests and watches for requests whose handler is #RateLimited.
#Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
#Autowired
private final RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
Add the following controller advice in your application.
#ControllerAdvice
public class ApplicationControllerAdvice {
#InitBinder
#RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
The #RateLimited should call the class RateLimitingAspect. So, after this all the constraints validator will be called.
See if it's feasible for you to implement similar logic for ##AfterThrowing advice as well which will have similar pointcut.
I have a requirement of achieving the method of controller in interceptor and the object returned by the method in interceptor.
why?
Because I want to declare the datatype which will return to client using annotation annotated on the method. for example :
#Controller
#Scope("prototype")
#RequestMapping("/hello/")
public class HelloWorld {
#ResponseType(DataType.JSON)
#RequestMapping(value="/{username}")
public UserInfo hellowUser(#PathVariable("username") String username) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
return userInfo.
}
}
then interceptor:
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throw Exception {
Method method = getRequestedMethod();
Object result = getResultReturnedByTheMethod();
ResponseType responseType = method.getAnnotation(ResponseType.class);
DataType type = responseType.value();
swich(type) {
case DataType.JSON : writeJson(result);
case .......
...
}
}
So, in another words, how can I implement the "getRequestedMethod" and "getResultReturnedByTheMethod" correctly?
Have you tried the Jackson processor? http://jackson.codehaus.org/
It automatically converts JSON to and from the controller. And is supported by Spring MVC.
My controller method is returning a ModelAndView, but there is also a requirement to write a cookie back to client. Is it possible to do it in Spring? Thanks.
If you add the response as parameter to your handler method (see flexible signatures of #RequestMapping annotated methods – same section for 3.2.x, 4.0.x, 4.1.x, 4.3.x, 5.x.x), you may add the cookie to the response directly:
Kotlin
#RequestMapping(["/example"])
fun exampleHandler(response: HttpServletResponse): ModelAndView {
response.addCookie(Cookie("COOKIENAME", "The cookie's value"))
return ModelAndView("viewname")
}
Java
#RequestMapping("/example")
private ModelAndView exampleHandler(HttpServletResponse response) {
response.addCookie(new Cookie("COOKIENAME", "The cookie's value"));
return new ModelAndView("viewname");
}
Not as part of the ModelAndView, no, but you can add the cookie directly to the HttpServletResponse object that's passed in to your controller method.
You can write a HandlerInterceptor that will take all Cookie instances from your model and generate the appropriate cookie headers. This way you can keep your controllers clean and free from HttpServletResponse.
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
for (Object value : modelAndView.getModel().values()) {
if (value instanceof Cookie)
res.addCookie((Cookie) value);
}
}
}
}
NB . Don't forget to register the interceptor either with <mvc:interceptors> (XML config) or WebMvcConfigurer.addInterceptors() (Java config).
RustyX's solution in Java 8:
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception{
if (modelAndView != null) {
modelAndView.getModel().values().stream()
.filter(c -> c instanceof Cookie)
.map(c -> (Cookie) c)
.forEach(res::addCookie);
}
}
}