Can I apply #ControllerAdvice to a view controller? - java

I am trying to set a default model value for all of my controllers, which is interpreted by my HTML template's layout (this is to add a top banner to all pages, such as to warn about upcoming maintenance). I wrote an #ControllerAdvice class with an #ModelAttribute method, and this works correctly on all of my custom controllers.
However, it does nothing when I visit a mapping registered directly with the ViewControllerRegistry; the method is simply never called, and ParameterizableViewController seems to bypass the normal binding and model generation.
Is there a way to write advice that will get applied to view controllers as well as custom controllers?

Is there a way to write advice that will get applied to view
controllers as well as custom controllers?
For your particular use case, I recommend to register a HandlerInterceptor and add your common model attributes to ModelAndView instance in postHandle method. Something like following:
public class CommonModelInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
modelAndView.addObject("Favorite Quote", "Welcome to the real world");
// Go crazy with modelAndView
}
}
Also, don't forget to register your interceptor:
public class WebConfig extends WebMvcConfigurerAdapter {
// Usual stuff
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CommonModelInterceptor());
}
}

#ControllerAdvice and other related annotations are handled by a RequestMappingHandlerAdapter registered automatically when you use #EnableWebMcv or
Other handling methods e.g resources or view controllers are handled by HttpRequestHandler and SimpleControllerHandlerAdapter which have no knowlege of #ControllerAdvice.
You can write a custom HandlerInterceptor which is application wide to handle the common logic and use the addInterceptors method of WebMvcConfigurer to register the interceptor

Related

Spring Security: How to get endpoint's authorities in Filter

In spring boot project I have a controller which has an endpoint with #PreAuthorize annotation
#RestController
#RequestMapping("/path")
class SomeController {
#PostMapping
#PreAuthorize("hasAuthority('SOME_AUTHORITY')")
public ResponseEntity<Object> doSomething() {
}
}
And also I have a filter that extends the OncePerRequestFilter
public class AuthTokenFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain f) throws IOException, ServletException {
//here I want to get the authorities specified in the controllers' endpoints
}
}
The question is how to get controllers' endpoints' authorities specified by the #PreAuthorize annotation in the above filter?
I recommend to move hasAuthority('SOME_AUTHORITY') into separate method in spring bean and use that method in filter and in #PreAuthorize annotation. In that way you will have common place for validate logic and you will keep code consistency as well.
Let me know if you need code examples.
You can do this in Spring Security's way, by using Pointcuts similar to AuthorizationMethodPointcuts. AuthorizationManagerBeforeMethodInterceptor reads these annotations using this pointcut.
GitHub link to spring-security for reference.
https://github.com/spring-projects/spring-security/tree/main/core/src/main/java/org/springframework/security/authorization
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses. Filters typically do not themselves create responses, but instead provide universal functions that can be "attached" to any endpoint.
OncePerRequestFilter . Spring guarantees that the OncePerRequestFilter is executed only once for a given request.
In your case you are extending OncePerRequestFilter in AuthTokenFilter and as you mentioned in comment you are authorizing user in this filter if user have valid details (in this case setting Authentication object).
Once you will hit your endpoint Spring will execute AuthTokenFilter logic
If details will valid then AuthTokenFilter will set Authentication in Spring context.
Spring Security supports authorization at the method level with help of different type of annotation like #PreAuthorize, #Secured . But enable annotation based security, we need to add the #EnableGlobalMethodSecurity annotation on any #Configuration class. example :
#Configuration
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfiguration {
//default configuration class
}
HttpSecurity is tied to URL endpoints while #PreAuthorize is tied to controller methods and is actually located within the code adjacent to the controller definitions.
authorities/roles should set in Authentication object inside AuthTokenFilter (in your case) and those role can validated with #PreAuthorize annotation.
#PreAuthorize will trigger after AuthTokenFilter filter

OPTIONS request handler should be called before API handler

I have a working #RestController component that yields API web endpoints.
This is one of those endpoints
#CrossOrigin
#GetMapping(API_VERSION + PLAYER + METHOD_FETCH + "/{uid:^[0-9]*$}")
public Player fetchPlayer(#PathVariable("uid") String uid) {
return mongoTemplate.findById(uid, Player.class);
}
Now when using my Vue.js App I call this endpoint. The problem is the axios http client library turns a get request that has authentication headers into a options request to probe the server for actual access.
Now I need to consume this options request and have it be enabled for CORS. I did the following therefore:
#RestController
#Log
#RequestMapping("/**")
public class AuthenticationEndpoint {
#CrossOrigin
#RequestMapping(method = RequestMethod.OPTIONS)
public void handleOptionRequest(){
log.info("option request handled");
}
}
I map it to every url so it "should" intercept every OPTIONS request. But it does not. When having a
GET http://{{host}}:80/api/v0.1/player/fetch/4607255831
Authorization: Basic MTIzNTM2NDMyNDphYmMxMjM=
The more specific API web endpoint is handled before the OPTIONS handler.
How can I actually put the OPTIONS handler before the others in Spring MVC?
I want it to act like an interceptor
OR
What is the best practise way to achieve the wanted behaviour? I kinda feel I am hacking around a better solution.
How can I actually put the OPTIONS handler before the others in Spring MVC? I want it to act like an interceptor.
Your can create a component a class that implements Filter interface and give it a High order :
#Component
#Order(1)
public class RequestInterceptor implements Filter {
#Override
public void doFilter
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String httpMethod = req.getMethod();
if(HttpMethod.OPTIONS.name().equals(httpMethod)){
//do something here before reaching the method handler
}
chain.doFilter(request, response);
}
// other methods
}
Or you can extends OncePerRequestFilter.java and make the same check as above in the doFilterInternal method.
EDIT
If you want to control whether to proceed handling a giving request or not you can use HandlerInterceptor :
A HandlerInterceptor gets called before the appropriate HandlerAdapter
triggers the execution of the handler itself. This mechanism can be
used for a large field of preprocessing aspects, e.g. for
authorization checks, or common handler behavior like locale or theme
changes. Its main purpose is to allow for factoring out repetitive
handler code.
HandlerInterceptor is basically similar to a Servlet
Filter, but in contrast to the latter it just allows custom
pre-processing with the option of prohibiting the execution of the
handler itself, and custom post-processing. Filters are more powerful,
for example they allow for exchanging the request and response objects
that are handed down the chain. Note that a filter gets configured in
web.xml, a HandlerInterceptor in the application context.
#Comonent
public class LoggerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception{
// do checks and decide wether to complete or to stop here
// true if the execution chain should proceed with the next interceptor or the handler itself.
// Else, DispatcherServlet assumes that this interceptor has already dealt with the response itself.
return true;
}
// other methods
}

Spring MVC Custom View

I am refactoring a legacy application to use Spring MVC. All of my controllers (legacy) return an object of type Model and my legacy dispatcher write the output of model.getContent(), the method getContent does internal processing and returns a json string. I have hundreds of controllers and do not want to rewrite them. Is it possible to write a custom view handler and include it in the spring servlet config?
Sample Controller:
public UserList extends BasicAction {
#Autowired
UserService userService;
#Autowired
UserCommand userCommand;
#Override
public Model getModel(Request req, Response resp)
throws ServletException, IOException {
Model model = new Model();
List<User> users;
try {
users = userService.getUsers((UserCriteria)userCommand.getResult());
model.addCollection(users);
model.setWrapper(new UserWrapper());
} catch (ValidationException e) {
e.printStackTrace();
} catch (WebCommandException e) {
e.printStackTrace();
}
return model;
}
}
I'm planning to annotate as #Controller. Specify the #RequestMapping or in the xml config, remove the base class BasicAction (legacy mvc). I've recently introduced spring to this project and refactored to use Dependency Injection and Request Scoped command objects (request wrappers)
The most straightforward is to implement View interface on your Model class. Then your legacy controllers can return this class directly (as they are now) and it will get rendered by DispatcherServlet via calling its render method.
Another possibility is to implement your own HandlerMethodReturnValueHandler, where the handler can actually render the response and mark the response as handled (mavContainer.setRequestHandled(true);) so that DispatcherServlet will not try to render any view.
I think what you want to do is create a custom ViewResolver that outputs your JSON response. This would be configured in Spring MVC to set the ViewResolver list, placing yours up top to have more precedence. The way it is supposed to work (from my recollection) is that Spring MVC will start at the top of the list, and try each ViewResolver until it finds one that returns the one that handles the return type. You will have to google how to make custom ViewResolvers, as I have only used them, never created one, but I know it's an interface so it should be do-able. I believe this would be the only way to do this that would not require any code changes in your controllers.
The more "preferred" method to do JSON, however, is to have Jackson in your classpath and simply return the object you want to serialize to JSON. Spring will auto-magically convert that to JSON, I believe using a ViewResolver they provide. But, I can surely relate to not wanting to refactor lots of working code.

how to get the mapped URL from controller name + action name in springmvc?

Is there existing solution to get mapped URL from (controller-name, action-name) in Spring MVC3, like UrlHelper in asp.net mvc or rails? I think it's very useful!
thx....
Probably, you want something like this:
in your #Controller class, you can add to your "action" method extra parameter of type HttpServletRequest.
Example:
#Controller
public class HelloWorldController {
#RequestMapping("/helloWorld")
public void helloWorld(HttpServletRequest request) {
//do call #getRequestURI(), or #getRequestURL(), or #getPathInfo()
}
}
With default configuration, Spring will "automagically" inject request, and then you can extract path info by calling one of HttpServletRequest#getPathInfo(), HttpServletRequest#getRequestUrl() methods (see explanation here: http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#method_summary)

How can I lookup the method being called on a Handler from a Spring HandlerInterceptor?

I have a Spring HandlerInterceptor intercepting the frontend URL's in my application (/app/*). I want to determine which action method in the Handler is about to be invoked from within the HandlerInterceptor. Is there a way to look that up, do I need to inject something into the interceptor that can look that up based on the requested path?
The Interceptor is like this:
public class PageCacheInterceptor implements HandlerInterceptor {...}
It is mapped like this:
<mvc:interceptors>
<bean class="com.example.web.interceptors.PageCacheInterceptor" />
</mvc:interceptors>
Background (because I know you'll ask!). I am adding simple page caching to my app and want to use an annotation like #Cacheable on each suitable method in the controller. The interceptor can then determine whether to cache a response based on the action that created it.
For example:
#RequestMapping(value = "", method = RequestMethod.GET)
#Cacheable(events={Events.NEW_ORDER,Events.NEW_STAT})
public String home(Model model) {...}
The events are the ones that cause the cache to be invalidated. For example /widget/list action would have it's cached response invalidated by a new widget being saved.
Edit: I've upgraded to the latest Spring 3.1 M2, as this blog post hinted at features I need, but it's not clear whether injecting these new classes or sub-classing them will be required. Has any one used them to retrieve the HandlerMethod in an interceptor?
Ok so the solution was actually really easy:
1) Upgrade to Spring 3.1
2) RTFM (properly)
For example a HandlerInterceptor can cast the handler from Object to HandlerMethod and get access to the target controller method, its annotations, etc
3) Cast the handler object to HandlerMethod in the Interceptor.
Then you can do this sort of thing:
HandlerMethod method = (HandlerMethod) handler;
Cacheable methodAnnotation = method.getMethodAnnotation(Cacheable.class);
if (methodAnnotation != null) {
System.out.println("cacheable request");
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Pre-handle");
HandlerMethod hm=(HandlerMethod)handler;
Method method=hm.getMethod(); if(method.getDeclaringClass().isAnnotationPresent(Controller.class)){
if(method.isAnnotationPresent(ApplicationAudit.class))
{
System.out.println(method.getAnnotation(ApplicationAudit.class).value());
request.setAttribute("STARTTIME",System.currentTimemillis());
}
}
return true;
}
This post has more details,hope this helps http://www.myjavarecipes.com/spring-profilingaudit-using-mvc-filters/

Categories

Resources