Spring boot and checking HTTP header presence - spring

I send along every REST call my custom header, which is (for instance) an authorization token. This token remains the same, as I do not need high security in this case. Can I use some simple way how to check every request coming to RestController whether it has this token among headers?

I can see a few ways:
Coding a #ModelAttribute in a #ControllerAdvice class, like this
#ControllerAdvice
public class HeaderFetcher {
#ModelAttribute
public void fetchHeader(#RequestHeader Optional<String> myHeader, Model model) {
if header is present
model.addAttribute("myHeader", the header);
else
throw exception
}
}
Haven't tried this, though.
Using a filter
Using AoP

Related

how to Enforce request header on all spring web RestController equests

Is there an option to specify a request header once in spring web RestController instead of doing it on every request?
e.q.
#RestController("workflowController")
public class MyClass{
public Value list(#RequestHeader(USER_ID_HEADER_PARAM) String user) {
...some code
}
public Workflow create(#RequestBody Workflow workflow, #RequestHeader(USER_ID_HEADER_PARAM) String user) {
... some code
}
}
the #RequestHeader(USER_ID_HEADER_PARAM) will be repeated in every request.
is there a way to specity it in the #RestCotroller level or the class level?
Thanks
Use some kind of filter class that can be configured to wrap around your requests in your servlets based on the URL path.
Here is info about the generic Servlet API filter API:
https://www.oracle.com/technetwork/java/filters-137243.html
If you're using Spring, there's another way to do it:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#filters
https://www.baeldung.com/intercepting-filter-pattern-in-java

Spring Boot REST controller not returning HTML string

I have a Spring Boot app that has one controller that serves mostly RESTful endpoints, but it has 1 endpoint that actually needs to return HTML.
#RestController
#RequestMapping("v1/data/accounts")
public class AccountResource {
// Half a dozen endpoints that are all pure data, RESTful APIs
#GetMapping("/confirmRegistration")
public void confirmRegistration(#RequestParam(value = "vt") String token) {
// Some logic goes here
System.out.println("This should work!");
return ResponseEntity.ok('<HTML><body>Hey you did a good job!.</body></HTML>')
}
}
When this runs, no errors/exceptions get thrown at all, and in fact I see the "This should work!" log message in my app logs. However from both a browser and a curl command, the HTTP response is empty. Any idea what I need to change in the ResponEntity builder to get the server returning a hand-crafted HTML string?
Add this to your #RequestMapping or #GetMapping
produces = MediaType.TEXT_HTML_VALUE
Spring defaults to application\json. If you need any other type, you need to specify it.

HttpServletResponse contained in servlet filter does not perform redirect

I am using Spring 4.0.6 in a servlet application. I have an abstract base controller with some general methods for all my controllers to use.
One of these methods is a redirect. I want to have a method with signature
redirect(String path)
To send a redirect, I am using
response.sendRedirect(response.encodeRedirectURL(path));
As I would like to keep method signatures short and clean, I need to get access to the response object inside the superclass method.
In order to do this, I've followed a suggestion found online, and defined a servlet filter with a ThreadLocal HttpServletResponse.
public class ResponseFilter extends OncePerRequestFilter {
private static final ThreadLocal<HttpServletResponse> responses = new ThreadLocal<HttpServletResponse>();
public static HttpServletResponse getResponse() {
return responses.get();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
responses.set(response);
} finally {
try {
filterChain.doFilter(request, response);
} finally {
responses.remove();
}
}
}
}
As I am using Spring security with a Java configuration, I'm adding this filter in my WebSecurityConfigurerAdapter subclass:
.addFilterAfter(rf, SwitchUserFilter.class)
Note that I have also tried adding the filter as first in the filterchain, and that I have tried using an Interceptor instead. All with the same results.
I have compared hashcodes on the response objects, and near as I can tell, the hashcodes match, but the redirect seems to be ignored. I have also looked at object ids on breakpoints in Eclipse, and there again, I have a match. The symptom is that the spring DispatcherServlet enters processDispatchResult and seems to think it needs to resolve a view. That view does not exist, as I expect to do a redirect:
javax.servlet.ServletException: File "/WEB-INF/views/application/redirecttest.jsp" not found
I have noticed that, if I add the response object back in my requestmapping controller method signature, the superclass redirect seems to work (even though I do not use the controller method response object at all).
Unfortunately, this behavior is reproducible both on a Mac and on Linux. I use Tomcat 7 as container.
Your filter should work just fine, but the problem you're facing is another. If you are using views (as you appear to do in the example) you need to return a redirect view from your controller in order to force a redirect; just instructing the response object to redirect won't work because Spring MVC infrastructure will try to do its thing (i.e. view resolution) before the Response is returned to the Servlet container.
For instance, if you use the convention to return the view name as a String from your controller method, you need to do the following in your controller:
#RequestMapping("/redirectTest")
public String redirectTest() {
return "redirect:http://www.example.com";
}

How to generically authorize or validate a JSON rest request based on the authenticated user and an attribute of the requestbody

My current Spring3 REST JSON api is authenticated with the default InMemory properties file/basic-authentication authentication manager. That has worked fine thus far, but I need to further validate that an incoming request is allowed to be made for that user. The Role concept seems to work fine as a gateway for entry to a particular controller's url, but it doesn't go far enough to validate that the user is permitted to ask for the data being requested.
In my app, each B2B partner that will be making requests to the API is assigned an applicationId. That partner user account is only allowed to make requests for that applicationId. The applicationId is passed as an attribute of the RequestBody POJO for all the POST API messages. I would like to decline requests that are made for improper applicationIds.
How can I validate that the authenticated user is making a permitted request?
I've started down the path of creating a custom AuthenticationProvider, but I don't know how to get access to the applicationId within the RequestBody bean that hadn't been marshalled into the java bean yet.
Perhaps a custom AuthenticationProvider isn’t the right solution, and a request validator of some sort is needed. If so, how would the validator on the appId attribute get access to the Principal (authenticated user object)
With any solution, I would like it be invisible to the controller, so that requests that do make it to the controller are permitted ones. Also, ideally, the solution should not depend on an engineer to remember some annotation to make the logic work.
Thanks in advance,
JasonV
EDIT 1: By implementing an InitBinder in the controller, and using the #Valid annotation on the RequestBody I was able to validate a request. However, this is not the Droids (er I mean solution) I'm looking for. I need to find a more generic way to handle it without all those Binders and annotations; too much to remember and spread around the application over dozens of request controllers, and it will be forgotten in the future.
The usual way to implement this is using #PreAuthorize.
#PreAuthorize("hasRole('USER') and authentication.principal.approvedAppId == #dto.applicationId")
#RequestMapping...
public ... someMethod(#RequestBody Dto dto, ...)
If you're worried about the repetition of the SpEL, define a new annotation like #PreAuthorizeUser and set the #PreAuthorize as a meta-annotation on it.
I was able to utilize an aspect to solve the problem generically.
I would still like to see if it is possible to do the following:
Get a marshalled RequestBody from the request object in the context of an AuthenticationProvider.
Here is the aspect code for future help to others.
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut(
"execution(org.springframework.http.ResponseEntity *(.., #org.springframework.web.bind.annotation.RequestBody (*),..))")
public void methodPointcut() {
}
#Around("controllerBean() && methodPointcut()")
public Object beforeMethodInControllerClass(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
long requestAppId = Long.parseLong(BeanUtils.getProperty(args[0], "applicationId"));
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User principal = (User) auth.getPrincipal();
String username = principal.getUsername();
long[] approvedAppIds = getApprovedAppIdsForUsername(username);
for (long approvedAppId : approvedAppIds) {
if (approvedAppId == requestAppId) {
isAllowedAccess = true;
break;
}
}
if (isAllowedAccess) {
return jp.proceed(args);
} else {
LOGGER.warn("There was an attempt by a user to access an appId they are not approved to access: username="+username+", attempted appId="+requestAppId);
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}

How to configure which controllers Spring #ControllerAdvice will be applied to?

I have two types of controllers in my spring application.
View controllers that forward to views to generate HTML
API controllers that return JSON directly from the controllers
Both the API and View controllers are part of the same spring dispatcher servlet. Spring 3.2 introduced the #ControllerAdvice annotation to allow for a global location to handle exception.
The documentation implies that #ControllerAdvice will be applied to every controller associated with a Dispatcher Servlet.
Is there a way to configure which controllers #ControllerAdvice will apply to?
For example in my scenario I want a #ControllerAdvice for my View Controllers and separate #ControllerAdvice for my API controllers.
For people that will still find this question:
As from Spring 4 ControlerAdvice's can be limited to Controler's with the specified annotations. Take a look at:
http://blog.codeleak.pl/2013/11/controlleradvice-improvements-in-spring.html
(second half of this article) for more details.
UPDATE
I am using spring 4. You can do one of 2 below options.
(1) You can add the packages you want. (Inside those packages you have controllers that you want to follow #ControllerAdvice).
Ex:
#ControllerAdvice(basePackages={"my.pkg.a", "my.pkg.b"})
(2) You can directly add the controller classes you want.
Ex:
#ControllerAdvice(basePackageClasses={MyControllerA.class, MyControllerB.class})
I do not think this is possible now. If you can make the API and View controllers throw different Exception types, then you could define two different #ExceptionHandlers and achieve what you want.
// For handling API Exceptions
#ExceptionHandler(APIException.class) // Single API Exception
#ExceptionHandler({APIException.class, ..., ,,,}) // Multiple API Exceptions
// For handling View Exceptions
#ExceptionHandler(ViewException.class) // Single View Exception
#ExceptionHandler({ViewException.class, ..., ...}) // Multiple View Exceptions
You could use aop to translate the Exceptions coming out of APIs to a standard APIException. See this thread on spring forums.
Hope it helps.
Your exceptions should not dictate the content-type of your response. Instead check the request's Accept header for what the browser expects.
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
...
String header = request.getHeader("Accept");
if(supportsJsonResponse(header)) {
//return response as JSON
response.setContentType(JSON_MEDIA_TYPE.toString());
return Json.stringify(responseMap);
} else {
//return as HTML
response.setContentType("text/html");
}
#ExceptionHandler(value=Exception.class)
public ModelAndView error(Exception ex) {
return new ModelAndView("redirect:/error/m");
}
...//ErrorController
#RequestMapping(value = "/m", produces="text/html")
public ModelAndView error()...
#RequestMapping(value = "/m", produces="application/json")
#ResponseBody
public Map errorJson()...

Resources