I am often asked some questions, for example, the student who writes Java server asks: WHY can’t the front end get the data when the server clearly returns correctly and the test environment debug can see it? When I set withCredentials=true, I still can’t get the cookies on the server.

So I decided to go through the whole process of using CORS to solve cross-domain problems. What do I do on the front and back end? Why do you do that?

Why is there a cross-domain problem?

To ensure user information security, all browsers follow the same origin policy. When is the same origin considered? What is the same origin policy?

Remember: The protocol, domain name, and port number are identical

See Web Security-Browser same-origin policy

Under the same-origin policy, the following restrictions apply:

  • Unable to obtain non-homogeneous Cookie, LocalStorage, SessionStorage, etc
  • A non-homologous DOM cannot be obtained
  • Unable to send Ajax requests to non-cognate servers

However, we often encounter the situation that the front and back end are separated, not under the same domain name, and need Ajax to request data. Then we have to circumvent that restriction.

You can find many cross-domain solutions on the Internet, some of which are quite old. Currently, jSONP and CORS (cross-domain resource sharing) are more commonly used in projects. This paper mainly talks about the principles and specific practices of CORS.

CORS cross-domain principle

The cross-domain principle of CORS is that the browser and server make some conventions and restrictions through some HTTP protocol headers. You can see HTTP- Access Control (CORS)

Protocol headers associated with cross-domain

Request header instructions
Origin Indicates the source URI of the precheck request or actual request, regardless of whether the ORIGIN field is always sent across domains
Access-Control-Request-Method Tell the server the HTTP method used for the actual request
Access-Control-Request-Headers Tells the server the header field carried by the actual request
Response headers instructions
Access-Control-Allow-Origin Specifies the outdomain URI that is allowed to access the resource. Wildcard * is not allowed for credential requests
Access-Control-Expose-Headers Specifies the response header accessible to the getResponseHeader of XMLHttpRequest
Access-Control-Max-Age Specifies how long the results of a preflight request can be cached
Access-Control-Allow-Credentials Whether to allow the browser to read the content of the response;

When used in response to a preflight precheck request, specifies whether the actual request can use the credentials
Access-Control-Allow-Methods Specifies the HTTP method allowed for the actual request
Access-Control-Allow-Headers Specifies the header field allowed in the actual request


The code examples

So I’ve written a little demo here, step by step. The directory is as follows:

. ├ ─ ─ the README. Md ├ ─ ─ client │ ├ ─ ─ index. The HTML │ └ ─ ─ request. Js └ ─ ─ server ├ ─ ─ pom. The XML ├ ─ ─ server - web │ ├ ─ ─ pom. The XML │ ├ ─ ─ Server - web. On iml │ └ ─ ─ the SRC │ └ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ com │ │ └ ─ ─ example │ │ └ ─ ─ cors │ │ ├ ─ ─ constant │ │ │ └ ─ ─ Constants. Java │ ├─ Controller │ ├─ Java │ ├─ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Resources │ │ └ ─ ─ the config │ │ └ ─ ─ applicationContext - core. XML │ └ ─ ─ webapp │ ├ ─ ─ WEB - INF │ │ ├ ─ ─ the dispatcher - servlet. XML │ │ ├ ─ ├ ─ sci-1.txt ├ ─ sci-1.txt ├ ─ sci-1.txtCopy the code


  • Client: Front-end, simple Ajax requests

In the client folder, start the static server, front page via http://localhost:8000/index.html access:

anywhere -h localhost -p 8000
Copy the code


  • Server: Java project, SpringMVC

Local start tomcat in IntelliJ IDEA, set up the host: http://localhost:8080/, the server-side data via http://localhost:8080/server/cors requests.

Because of the different port numbers of the front-end and back-end, there are cross-domain restrictions. Here, CORS is used to solve the problem that cross-domain data cannot be requested through Ajax.


There is no case where cross domains are allowed

This is a situation where the front end does nothing and the server side does nothing.

Client: After the request is successful, the data is displayed on the page

new Request().send('http://localhost:8080/server/cors', {success: function(data){
		document.write(data)
	}
});
Copy the code

Server:

@Controller
@RequestMapping("/server")
public class CorsController {
    @RequestMapping(value="/cors", method= RequestMethod.GET)
    @ResponseBody
    public String ajaxCors(HttpServletRequest request) throws Exception{
        return "SUCCESS"; }}Copy the code

In your browser’s address bar enter http://localhost:8080/server/cors direct request to the server, you can see return results: ‘SUCCESS’

In your browser’s address bar enter http://localhost:8000/index.html, web pages from different domain send an ajax request to the Server. Several aspects can be seen:

As you can see from network, the request returned normal.

However, there is no content in Response and Failed to load Response data is displayed.

And the console reported an error:

Conclusion:

1, the browser request is sent, the server will return the correct, but we can not get the response content

2, the browser console complains tips can do, and prompt very understand: XHR cannot request http://localhost:8080/server/cors, requested resource is not set in the response header Access – Control – Allow – Origin, Origin: http://localhost:8000 does not allow cross-domain requests.

The next step is to set the access-Control-Allow-Origin header when the server responds to a cross-domain request

Set access-Control-allow-origin to Allow cross-domain

Access-control-allow-origin = access-control-allow-origin = access-Control-allow-origin Access-control-allow-origin = access-Control-allow-origin = access-Control-allow-origin = access-Control-allow-origin = access-Control-allow-origin = access-Control-allow-origin = access-Control-allow-origin

The access-Control-allow-Origin response header specifies whether the resources in the response are allowed to be shared with the given Origin.

Access-control-allow-origin values are as follows:

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin:
Copy the code

Access-control-allow-origin = access-Control-origin = access-Control-allow-origin = access-Control-allow-origin

1. Add a filter

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin"."http://localhost:8000");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    public void destroy(a) {}}Copy the code

2. Add the filter configuration to the web.xml file:

<filter>
     <filter-name>crossDomainFilter</filter-name>
     <filter-class>com.example.cors.filter.CrossDomainFilter</filter-class>
</filter>
<filter-mapping>
      <filter-name>crossDomainFilter</filter-name>
      <url-pattern>/ *</url-pattern>
</filter-mapping>
Copy the code

3, and then restart the tomcat, the client sends a request again at http://localhost:8000/index.html

Access-control-allow-origin = access-Control-allow-origin = access-Control-allow-origin = access-Control-allow-origin http://localhost:8000, this should be consistent with the origin in the request header, or access-Control-allow-origin :* can also be set, which allows any website to Access the resource (provided that there is no credential information, this will be described later).

This allows a simple cross-domain request by setting the access-Control-Allow-Origin header on the server.

Simple request vs. precheck request

Access-control-allow-origin a simple request can be made by setting the response header access-Control-Allow-Origin on the server.

What kind of request is a simple request? What is the equivalent of a simple request? What’s different about the way you solve cross-domains?

A simple request can be considered if it meets the following conditions:

1. Use one of the following HTTP methods

-get-head-post, and the content-type value is one of the following: -text/plain-multipart/form-data-application /x-www-form-urlencodedCopy the code

And the request header contains only the following

-accept-accept-language-content-language-content-type -dpr - Downlink - save-data - viewport-width - DPR - Downlink - save-data - viewport-width -  WidthCopy the code

If the request does not meet the preceding requirements, the browser must send a precheck request before sending a formal request. The precheck request is sent in the OPTIONS method. The browser can determine whether to send the precheck request based on the request method and request header.

For example, the Client sends the following request:

new Request().send('http://localhost:8080/server/options', {method: 'POST'.header: {
		'Content-Type': 'application/json'  // Tell the server what type of data is actually being sent
	},
	success: function(data){
		document.write(data)
	}
});
Copy the code

Controller for Server handling requests:

@Controller
@RequestMapping("/server")
public class CorsController {
    @RequestMapping(value="/options", method= RequestMethod.POST)
    @ResponseBody
    public String options(HttpServletRequest request) throws Exception{
        return "SUCCESS"; }}Copy the code

Header is inserted in the request header, ‘Content-Type’: ‘application/json’. The browser will issue a precheck request with the OPTIONS method. The browser will add the following headers to the request:

Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST
Copy the code

The purpose of this precheck Request is to tell the server that I will send a Request Header with a Content-type in the POST method in the following Request and ask the server whether to allow it. .

Here the server has not done anything to allow this request, so the browser console is reporting an error:

The server does not tell the browser to Allow content-type in the response to the pre-check request. That is, the server needs to set access-Control-allow-headers to Allow the browser to send requests with content-type.

Add access-control-allow-headers to the Server filter.

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin"."http://localhost:8000");
        resp.setHeader("Access-Control-Allow-Headers"."Content-Type");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    public void destroy(a) {}}Copy the code

You can see that the request succeeded

The first time the precheck request is sent, the browser sets the request header:

Access-control-request-headers :content-type // Request Headers added to the Request access-Control-request-method :POST // Cross-domain Request MethodCopy the code

The server sets the response header:

Access - Control - Allow - Headers: the content-type / / Allow the header Access - Control - Allow - Origin: / / http://localhost:8000 Allow cross-domain sourceCopy the code

Access-control-allow-methods can also be set to limit client request Methods.

When the precheck request succeeds, the browser issues a second request, which is the actual request for data:

You can see that the POST Request is successful. The access-Control-request-headers and access-Control-request-method are not set for the second Request.

But there is a problem here. When you need to precheck the request, the browser will issue two requests, one OPTIONS and one POST. Both times the data was returned. In this way, if the server logic is complicated, for example, to search for data in the database, the logic from the Web layer and service to the database will go through twice, and the browser will get the same data twice. Therefore, the filter of the server can be changed. After setting the cross – domain request response header, it returns directly without following the logic.

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin"."http://localhost:8000");
        resp.setHeader("Access-Control-Allow-Headers"."Content-Type");   
        // The OPTION request is returned directly
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if (req.getMethod().equals("OPTIONS")) {
            resp.setStatus(200);
            resp.flushBuffer();
        }else{ filterChain.doFilter(servletRequest,servletResponse); }}public void destroy(a) {}}Copy the code

Conclusion:

1. If the content-Type of the response header for a POST request is set to some value, or if the response header for a custom request is set to some value, the browser will first send a precheck request using OPTIONS and set the corresponding request header.

2. The server still returns normally, but if no corresponding response header is set in the response header of the precheck request, the precheck request will not pass and the second request will not be issued to obtain data.

3, the server set the corresponding response header, the browser will send a second request, and the data returned by the server spit out, we can get the response content

Request with credential information

There’s another situation that we see all the time. When a browser sends a request, it needs to send a cookie to the server. The server performs some authentication according to the information in the cookie.

By default, browsers send Ajax requests to different domains without sending cookie information.

Client:

var containerElem = document.getElementById('container')
new Request().send('http://localhost:8080/server/testCookie', {success: function(data){
		containerElem.innerHTML = data
	}
});
Copy the code

Server:

@RequestMapping(value="/testCookie", method= RequestMethod.GET)
@ResponseBody
public String testCookie(HttpServletRequest request,HttpServletResponse response) throws Exception{
    String str = "SUCCESS";
    Cookie[] cookies = request.getCookies();
    String school = getSchool(cookies);
    if(school == null || school.length() == 0){
        addCookie(response);
    }
    return str + buildText(cookies);
}
Copy the code

The server receives the request, determines whether there is a school in the cookie, and adds the cookie if there is no school.

You can see that there is a set-cookie in the response header. When a request is made again, if it is a same-origin request, the browser will put the value of set-cookie in the request header, but for cross-domain requests, this Cookie is not sent by default.

To enable the browser to send cookies, you need to set the withCredentials attribute of XMLHttpRequest to true on the Client.

Client:

var containerElem = document.getElementById('container')
new Request().send('http://localhost:8080/server/testCookie', {withCredentials: true.success: function(data){
		containerElem.innerHTML = data
	}
});
Copy the code

The browser now adds cookie information to the request header

But the server returned data is not displayed in the page, and an error is reported:

The error message is clear: When a request contains Credentials, you need to set the access-Control-allow-credentials header. The withCredentials attribute of XMLHttpRequest controls whether the request contains Credentials. ** So we add the response header to the Server filter:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin"."http://localhost:8000");
        resp.setHeader("Access-Control-Allow-Credentials"."true");
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if (req.getMethod().equals("OPTIONS")) {
            resp.setStatus(200);
            resp.flushBuffer();
        }else{ filterChain.doFilter(servletRequest,servletResponse); }}Copy the code

Now that the browser knows that the access-Control-allow-credentials in the response header are true, it will spit out the data, and we can get the content from the response.

What about with the credential information and the pre-check request? If there are pre-check requests withCredentials (XMLHttpRequest’s withCredentials are set to true), the server needs to set access-control-allow-credentials: True, otherwise the browser will not issue a second request and report an error.

Conclusion:

1. When making cross-domain requests, the browser does not send cookies by default. You need to set the withCredentials attribute of XMLHttpRequest to True

2. The browser sets the withCredentials attribute of XMLHttpRequest to true, indicating that credential information (cookies in this case) is to be sent to the server. The server then needs to add true to the access-Control-allow-credentials in the response header. Otherwise there are two cases on the browser:

  • If it’s a simple request, the server spits out the result, the browser gets it but doesn’t spit it out, and reports an error.
  • If it is a pre-check request, we also cannot get the return result, and an error message is reported indicating that the pre-check request does not pass, and the second request will not be sent.

other

Cookie same-origin policy

The other problem is that when the withCredentials attribute of XMLHttpRequest is set to true, the browser sends the cookie and the server still cannot retrieve it.

Cookies also follow the same origin policy. When setting cookies, you can find that in addition to the key-value pair, you can also set these values of cookies:

Cookie attribute values instructions
path The path to the accessible cookie, which defaults to the current document location
domain The domain name accessible to the cookie, which defaults to the domain part of the path to the current document location
max-age How long does it take to fail? Seconds.

Negative: valid within the session. 0: deletes cookies. Positive: The validity period is creation time + max-age
expires Cookie expiration date. If not defined, the cookie expires at the end of the conversation, known as a session cookie
secure Cookies are transmitted only through HTTPS

If the cookie cannot be obtained, check the domain and path of the cookie.

Internet Explorer has no permission for cross-domain access

Prompt no permissions when sending ajax requests across domains. By default, Internet Explorer restricts cross-domain access. You need to remove restrictions in your browser Settings. Methods: Settings > Internet Options > Security > Custom Levels > Find Others in Settings – Enable Access to data sources by domain in Others.

The Demo source code

CORS Demo source

reference


Did you get anything? See this one? You may not know the wild history of JavaScript modularity