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