Modify response message header in spring Interceptor

Source: Internet
Author: User
Tags browser cache


Tag: Doc IAT Code frame allows dap therefore dev before


Problem description


The front and back end of the project, using Vue, the back end uses spring MVC.
Obviously, there is a need to address the browser's cross-domain Access data limitation, which is resolved using the Cros protocol.
Since the project I joined in the mid-term, mainly responsible for integrating the Shiro framework into the project as a rights management component, the other colleagues have already written some of the interfaces, I am responsible for writing some new interfaces.
Previous colleagues resolved cross-domain issues using the annotations provided by spring@CrossOrigin:


 
 
@RequestMapping(value = "/list.do", method = RequestMethod.GET)
@ResponseBody
@CrossOrigin(origins="*")
@RequiresPermissions({"edge:manage"})
public JSONObject deviceList(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // do something
    return new Object();
}


When I entered the project, I thought it was too cumbersome, and I needed to use annotations explicitly in each of the controller methods@CrossOrigin.
So I'm going to use filter to solve my newly written part of this interface, as follows:


public class CROSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
String origin = req.getHeader("Origin");
if(origin == null) {
String referer = req.getHeader("Referer");
if(referer != null) {
origin = referer.substring(0, referer.indexOf("/", 7));
}
}
Resp.setheader ("access control allow origin", origin); / / allows the specified domain to access cross domain resources
resp.setHeader("Access-Control-Allow-Credentials", "true");
if(RequestMethod.OPTIONS.toString().equals(req.getMethod())) {
String allowMethod = req.getHeader("Access-Control-Request-Method");
String allowHeaders = req.getHeader("Access-Control-Request-Headers");
Resp.setheader ("access control Max age", "86400"); / / browser cache pre check request result time, unit: Second
Resp.setheader ("access control allow methods", allowmethod); / / the actual request method name that the browser is allowed to send after the pre check request succeeds
Resp.setheader ("access control allow headers", allow headers); / / request headers that allow browsers to send
Return;
}
chain.doFilter(request, response);
}
} 


OK, so far, access to my newly written interface is no problem, but access to the interface that the colleague has written before, in the browser console error:


Failed to load http://10.100.157.34:8080/devicemanager/device/list.do: The ‘Access-Control-Allow-Origin‘ header contains 
multiple values ‘http://192.168.252.138:8000, http://192.168.252.138:8000‘, but only one is allowed. 
Origin ‘http://192.168.252.138:8000‘ is therefore not allowed access.
main.js:162 Error: Network Error
    at FtD3.t.exports (createError.js:16)
    at XMLHttpRequest.f.onerror (xhr.js:87)


According to the log description, the client error is because the response message header returned by the serverAccess-Control-Allow-Origincontains 2 values.


Cause of error


The project involves cross-domain access to data, as well as the need to pass cookies across domains, and, according to the Cros protocol, the response message headerAccess-Control-Allow-Originvalue can only be specified for a single domain name (Note: cannot be a wildcard "*").
However, the response message header returned by the server nowAccess-Control-Allow-Origincontains multiple values that the client considers to be non-compliant with the Cros protocol, so an error is made.
So why do you return multiple values? Because the request was already set once in the filter I wrote, and the controller method was@CrossOriginadded once with spring annotations.


Solutions


Since it is illegal to return multiple values for the same message header, it is necessary to control the service side to return only one value, which is the idea and direction to solve the problem.
Obviously, it is not possible to achieve this goal in filter.


1. Modify the response message header using the Spring Interceptor


The first idea is to modify the response message header value after the Controller method executes by customizing the interceptor implementation, without making any modifications.


public class CrossFilter extends HandlerInterceptorAdapter {
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
//If the message header has been set, make sure that only one value is set
String originHeader = "Access-Control-Allow-Origin";
if(response.containsHeader(originHeader)) {
String origin = request.getHeader("Origin");
if(origin == null) {
String referer = request.getHeader("Referer");
if(referer != null) {
origin = referer.substring(0, referer.indexOf("/", 7));
}
}
response.setHeader("Access-Control-Allow-Origin", origin);
}
String credentialHeader = "Access-Control-Allow-Credentials";
if(response.containsHeader(credentialHeader)) {
response.setHeader("Access-Control-Allow-Credentials", "true");
}
}
} 


To add an interceptor configuration in spring:


<! -- Interceptor: intercepting a specific path -- >
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="org.chench.test.filter.CrossFilter" />
</mvc:interceptor>
</mvc:interceptors> 


However, debugging found: Although the Posthandle method has explicitly set the message header to a value, but returned to the browser client is still 2 values!
No solution!
So began Google related issues, and finally found a post: https://mtyurt.net/2015/07/20/spring-modify-response-headers-after-processing/.
Bloggers also want to add a response message header after the Controller method executes, but the way the spring interceptor is used is not effective.
The real reason is the limitations of the SPRINGMVC framework, see: Https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc.
Search for keywords in spring's documentation: Posthandle, see the following statement:


Note that postHandle is less useful with @ResponseBody and ResponseEntity methods for which a the response is written 
and committed within the HandlerAdapter and before postHandle. That means its too late to make any changes to the 
response such as adding an extra header. For such scenarios you can implement ResponseBodyAdvice and either declare it as 
an Controller Advice bean or configure it directly on RequestMappingHandlerAdapter.


What?@ResponseBodythe reason for the annotation is that it is not possible to modify the response message header by means of an interceptor.


2. Modify the response message header in Responsebodyadvice


Because the Controller method has already used@ResponseBodyannotations to return JSON data, the response message header cannot be modified through the spring interceptor.
But spring also provides a Responsebodyadvice interface that allows control of the response message header in this scenario.


@ControllerAdvice
public class HeaderModifierAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
Return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
ServletServerHttpRequest ssReq = (ServletServerHttpRequest)request;
ServletServerHttpResponse ssResp = (ServletServerHttpResponse)response;
if(ssReq == null || ssResp == null
|| ssReq.getServletRequest() == null
|| ssResp.getServletResponse() == null) {
Return body;
}
//Process response with no cross domain header added
HttpServletRequest req = ssReq.getServletRequest();
HttpServletResponse resp = ssResp.getServletResponse();
String originHeader = "Access-Control-Allow-Origin";
if(!resp.containsHeader(originHeader)) {
String origin = req.getHeader("Origin");
if(origin == null) {
String referer = req.getHeader("Referer");
if(referer != null) {
origin = referer.substring(0, referer.indexOf("/", 7));
}
}
resp.setHeader("Access-Control-Allow-Origin", origin);
}
String credentialHeader = "Access-Control-Allow-Credentials";
if(!resp.containsHeader(credentialHeader)) {
resp.setHeader(credentialHeader, "true");
}
Return body;
}
} 


OK, Perfect solution!
Of course, corresponding to the filter I write also need to adjust:


public class CROSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(logger.isDebugEnabled()) {
logger.debug(String.format("CORS filter do filter"));
}
//No more cross domain headers for all requests
//Only options requests are processed in filter, and cross domain message headers are placed in responsebodyadvice to solve the problem
if(RequestMethod.OPTIONS.toString().equals(req.getMethod())) {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
String origin = req.getHeader("Origin");
Resp.setheader ("access control allow origin", origin); / / allows the specified domain to access cross domain resources
resp.setHeader("Access-Control-Allow-Credentials", "true");
String allowMethod = req.getHeader("Access-Control-Request-Method");
String allowHeaders = req.getHeader("Access-Control-Request-Headers");
Resp.setheader ("access control Max age", "86400"); / / browser cache pre check request result time, unit: Second
Resp.setheader ("access control allow methods", allowmethod); / / the actual request method name that the browser is allowed to send after the pre check request succeeds
Resp.setheader ("access control allow headers", allow headers); / / request headers that allow browsers to send
Return;
}
chain.doFilter(request, response);
}
} 
Summarize


1. For projects that need to address cross-domain browser issues, the solution should be unified, either by using the filter or using@CrossOriginannotations, which must be planned from the outset.
And I have to use the above approach to solve the problem, is because the earlier has written a lot of code, do not want to modify, the last resort.
2. For scenes with@ResponseBodyannotations, if the response message header needs to be uniformly adjusted, it can only be done by customizing the Responsebodyadvice implementation.
3. It is recommended that you resolve cross-domain issues by using the filter instead of direct use of spring annotations@CrossOrigin, which is cumbersome.



Reference
Http://www.cnblogs.com/nuccch/p/7875189.html cross-domain request delivery cookie problem
Https://www.w3.org/TR/cors/Cross-Origin Resource Sharing
HTTPS://DOCS.SPRING.IO/SPRING/DOCS/CURRENT/SPRING-FRAMEWORK-REFERENCE/WEB.HTML#MVC SPRINGMVC Documentation



Modify response message header in spring Interceptor


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.