Spring 406 with a custom Configurer - java

I recently added jackson-dataformat-xml to the classpath, and magically spring started sending XML instead of JSON if the Accept header wasn't precised in the request. Since I'm dealing with spaghetti legacy code adding the header to all requests is not an option, I added a custom content negociation filter :
#Override
public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {
super.configureContentNegotiation(configurer);
configurer
.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
}
However, this backfired for a request like
#RequestMapping(value = "edit", method = RequestMethod.GET)
public ResponseEntity<?> edit(...) throws ApiException {
.... // returns an inputstream
}
Where I get a 406 error. If I remove the default content type, it works fine but then I get xml for other requests. How can I configure both?

Related

Resource blocked due to MIME type in Spring MVC when DELETE endpoint added

I'm developing a web app using Spring Boot v2.1.9.RELEASE and Thymeleaf. The app mixes server-side rendering (Spring MVC regular controllers) with rest controllers called through AJAX. The page works like a charm as long as GET and POST are the only HTTP methods I use. As soon as I add a - totally unrelated, yet unused - DELETE REST controller, CSS and JS resources stop being loaded and errors like the following are shown in browser console:
The resource from “http://localhost:8080/css/demo.css” was blocked due to MIME type (“application/json”) mismatch (X-Content-Type-Options: nosniff)
If I remove the new controller, the page starts working again.
When I inspect the request with Network Monitor, I observe that:
X-Content-Type-Options: nosniff header is always present in the response, which is consistent with Spring Security documentation.
if the DELETE endpoint is not there, a GET /css/demo.css request returns with a HTTP 200 code as expected. Both Accept (request header) and Content-Type (response header) are marked as text/css.
when I add the endpoint, the above request returns a HTTP 405. Accept header is text/css but Content-Type becomes application/json. Also, a new response header is added: Allow: DELETE.
My guess is that when Spring scans for controllers and a DELETE method is found, some extra configuration is automatically added, but I couldn't find any reference to confirm that. I would like to know why is this happening and how can I avoid this behaviour.
Since I'm developing local, my Spring Security config is pretty straightforward:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManagerConfigurer authConfigurer() {
return new InMemoryAuthenticationManagerConfigurer();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
authConfigurer().configure(auth);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The REST controller I'm adding does almost nothing:
#RestController
public class MotivosController {
private final String CONTROLLER_PATH = "/rest/motivos/{id}";
#RequestMapping(name=CONTROLLER_PATH, method=RequestMethod.DELETE)
public void deleteMotivo(#PathVariable(name="id") String id) {
logger.debug("Eliminando el motivo " + id);
}
// Other members I consider unrelated to the problem go here...
}
CSS and JS files are being loaded in a template.html file placed under src/main/resources/templates folder, as follows:
<link type="text/css" href="/css/demo.css" rel="stylesheet" />
Answer was easier than I thought and I was totally wrong with my assumptions. I confused #RequestMapping's name attribute with value attribute, and because of that any URL matches that method. Changing name for value made it work.

No cache header in Spring Boot response from RestController

I have a typical controller which returns JSON and I need to return no-cache header as a part of a response. Please note at this moment I have no spring security on classpath, however if there is need to make it to work out-of-the-box then I'm fine with this.
Currently I have implemented this in the following manner:
public static <T> ResponseEntity<T> createResponseSpringWay(final T body) {
org.springframework.http.CacheControl cacheControl = org.springframework.http.CacheControl.noCache();
return ResponseEntity.ok().cacheControl(cacheControl).body(body);
}
Is there any spring boot property that can make it automatically for me? I want to get rid of ResponseEntity at all methods.
I've tried the following configuration, but I believe it only applies to static resources, so it doesn't work:
spring.resources.cache.cachecontrol.no-store=true
spring.resources.cache.cachecontrol.must-revalidate=true
spring.resources.cache.cachecontrol.no-cache=true

how to Add a DefaultEndpoint if No endpoint mapping is found

In my Spring boot app replacing a legacy, i have defined a webservice EndPoint.
soem of the user today comes in with payload that does nothave the namespace URI.
since namespace is not there, Spring throws No Endpoint mapping found error.
Is there a way i can add a default Endpoint so that it will get invoked if no mapping is found.
Thanks
You can try the following to create a fallback method for all request
#RequestMapping(value = "*", method = RequestMethod.GET)
#ResponseBody
public String getFallback() {
return "Fallback for GET Requests";
}
You can get more information here https://www.baeldung.com/spring-requestmapping

How to return proper error response for content-type text/csv?

I have a servlet where in general I return text/csv as response. So a plain comma separated string.
BUT: in case of exceptions, I'd want to show just some error text as response. But instead Spring generates a custom ResponseEntity object, then tries to convert this response to csv which obviously fails.
Is it possible to replace the requested format to format=json, and then just return the default Spring error response?
#RestController
public class CsvServlet {
#RequestMapping(value = "/test", produces = "text/csv")
#ResponseBody
public String errorCsv(HttpServletRequest request) {
return "some, plain, text";
}
}
Usage: localhost:8080/test?format=csv
When having spring.security.enabled=true, this will first validate the basic auth credentials. If they fail, spring will automatically redirect to /error servlet.
Thereby BasicErrorController.error() method comes in, catching the error and generating a ResponseEntity with error attributes like timestamp, exception, path, etc.
Problem: now an Object of type ResponseEntity is returned. This is fine as long as the format parameter is either ?format=json/xml. As the response can then be properly converted.
But in my special case, where I request ?format=csv, the conversation will fail, leading to:
Response could not be created:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not
find acceptable representation
This is partially true, moreover misleading because the user should directly see that the authentication credentials have been invalid. Because in general I'm accepting csv, but Spring and the mappers don't know how to convert a ResponseEntity to a plain format like csv.
Question: how can I preserve the original exception? So that I could just return a plain text error message. It would also be fine if I could return a application/json response in this case!
Workaround as follows for the moment: override the /error servlet handler, and if the ?format parameter is not either json/xml, just return the original http status error without a body.
Thus spring cannot fail on converting the body into plain format and return the response correctly (but without exception details in the body).
#Controller
public class WorkaroundBasicErrorController extends BasicErrorController {
//default constructor from ErrorMvcAutoConfiguration
public DefaultBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
super(errorAttributes, serverProperties.getError(), errorViewResolversProvider.getIfAvailable());
}
#RequestMapping
#ResponseBody
#Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
String format = request.getParameter("format");
return (StringUtils.containsAny(format, "json", "xml"))
? super.error(request)
: new ResponseEntity<>(getStatus(request)); //neglect body for plain formats
}
}
Try to use a void #ExceptionHandler, and write error message directly in HttpResponse

Spring Security - Ajax calls ignoring #ExceptionHandler

I have a controller in my project that handles all exceptions defined like this:
#ControllerAdvice
public class GlobalExceptionHandlingController {
#ResponseBody
#ExceptionHandler(value = AccessDeniedException.class)
public ResponseEntity accessDeniedException() {
Logger.getLogger("#").log(Level.SEVERE, "Exception caught!");
return new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN);
}
}
I'm focusing on one specific exception here and that is AccessDeniedException that is thrown by Spring Security on unauthorized requests. This is working properly for "normal" aka non-ajax requests. I can click on a link or enter URL directly in the location bar and I will see this message if request is unauthorized.
However on AJAX request (using Angular for it) I'm getting standard 403 error page as a response but what's interesting is that I can see that AccessDeniedException is caught by this controller!
I did some research and it seems that I need to have custom AccessDeniedHandler so I made this:
Added this lines in my Spring Security configuration:
.and()
.exceptionHandling().accessDeniedPage("/error/403/");
and I made special controller just to handle this:
#Controller
public class AjaxErrorController {
#ResponseBody
#RequestMapping(value = "/error/403/", method = RequestMethod.GET)
public ResponseEntity accessDeniedException() {
return new ResponseEntity("Access is denied (AJAX)", HttpStatus.FORBIDDEN);
}
}
Now this is working fine but the exception is still caught in the first controller but return value of that method is getting ignored. Why?
Is this how it's supposed to be done? I have a feeling that I am missing something here.
I'm using Spring 4.2.5 with Spring Security 4.0.4.
Although I don't know all the details, my theory is that it can be a content type issue.
Often when doing AJAX requests, the response is expected to be in JSON, so the browser will add an Accept: application/json header to the request.
Your response entity on the other hand:
new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN)
is a text response, the default Content-Type of this with a typical Spring setup is text/plain.
When Spring detects that it can't deliver a response with type the client wants, it fallbacks to the default error page.

Categories

Resources