How do cross-domain problems arise?

The origin of the cross-domain problem

I believe that many people have more or less understanding of cross-domain issues, especially when the separation of front and back ends is popular nowadays.

You develop a front-end project locally, the project is run through node, the port is 9528, and the server is provided through spring boot, and the port number is 7001.

When you call a server interface, it is likely to get an error like the following:

request-blocked-by-cors

Then you debug where the request is sent, and where the exception occurs you will get this result:

blocked-error

The exception object is weird, the returned response is undefined, and there is only one "Network Error" in the message.

When you see this, you should know that you have encountered a cross-domain problem.

But you need to be clear that the request has been sent and the server has also received and processed it, but the returned response is not what the browser wants, so the browser intercepts the result of the response, which That's why the response you see is undefined.

Browser Same Origin Policy

Then why does the browser intercept the results returned by the server?

This requires us to understand that browsers are introduced based 同源策略(same-origin policy)on .

Back in 1995, Netscape introduced the "Same Origin Policy" in browsers.

The original "same-origin policy" mainly restricted the access of cookies. The cookies set by page A cannot be accessed by page B unless page B and page A are "same-origin".

So how to determine whether two web pages are "same origin"? The so-called "same origin" means that "protocol + domain name + port" are the same, even if two different domain names point to the same IP address, they are not the same origin.

the-compose-of-domain

No Same Origin Policy Protection

So why do this homology restriction? Because without the protection of the same-origin policy, the browser will have no security at all.

Lao Li is a fishing enthusiast. He often buys various fishing tools on the website I want to buy (51mai.com), and pays directly with the account password through the bank (yinhang.com).

This day, Lao Li bought another fishing rod on 51mai.com . After entering the bank account password and the payment was successful, he saw a "free fishing bait" posted by a website called Diaoyu (diaoyu.com) on the payment success page. advertise.

Without thinking, Lao Li clicked on the advertisement and jumped to a phishing website. Little did he know that this was really a "phishing" website, and all the money in Lao Li's bank account was transferred.

no-same-origin-policy

The above is the process of Lao Li's money being stolen:

1. Lao Li bought a fishing rod and logged in to the bank's website to enter the account and password to make payment. The browser cached the bank's cookie locally.

2. Lao Li clicked on the phishing website, and the phishing website used the cookie after Lao Li logged in to the bank, pretending that he was Lao Li and performed the transfer operation.

This process is the famous CSRF (Cross Site Request Forgery), cross-site request forgery, it is precisely because of the possible forged requests that the browser is insecure.

So how to prevent CSRF attacks, you can refer to this article: How to prevent CSRF attacks?

What behaviors are restricted by the same-origin policy

As mentioned above, the same-origin policy is a security mechanism that essentially restricts how a document or script loaded from one source interacts with resources from another source. This is an important factor for isolating potentially malicious files. Security Mechanism.

With the development of the Internet, the "Same Origin Policy" is becoming more and more strict, not only limited to the reading of cookies. Currently, a total of three behaviors are restricted if non-homologous.

(1) Cookies, LocalStorage and IndexDB cannot be read.

(2) DOM cannot be obtained.

(3) The response to the request is intercepted.

Although these restrictions are necessary, they are sometimes inconvenient, and reasonable uses are also affected. Therefore, in order to obtain non-"same-origin" resources, there is cross-domain resource sharing.

Cross-origin resource sharing

Seeing this, you should understand why the request at the beginning of the article is intercepted. The reason is that the source of the request and the source of the server are not "same origin", and the server does not set the allowed cross-domain resource sharing, so the response of the request is The browser is blocked.

CORS is a W3C standard, the full name is "Cross Origin Resource Sharing", which allows browsers to send XMLHttpRequest requests to cross-origin servers, thus overcoming the limitation that only same-origin requests can be sent.

CORS implementation mechanism

How is the cross-domain resource sharing mechanism implemented?

When a resource (origin) initiates a request to another resource (host) through a script, and the requested resource (host) and the request source (origin) are different sources (the protocol, domain name, and port are not all the same), the browser A cross-domain , and the browser will automatically add the domain of the current resource to a header called Origin in the request header.

Of course, there are three tags themselves that allow cross-origin loading of resources:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

For example, the home page of a website http://domain-a.com/index.html uses <img src=" http://domain-b.com/image.jpg " /> to load images on other domains, except In addition, there are ways such as importing css and js files through CDN nodes.

For security reasons, browsers restrict cross-origin HTTP requests from within scripts. For example, the XMLHttpRequest and Fetch APIs follow the same-origin policy. This means that a web application using these APIs can only request HTTP resources from the same domain from which the application is loaded, unless the correct CORS response headers are included in the response message.

By setting additional HTTP response headers in the response message to tell the browser that a web application running on a certain origin is allowed to access resources from different origin servers, the browser will not intercept the response. .

So what are these extra HTTP response headers?

response header Is it necessary meaning
Access-Control-Allow-Origin Yes This field indicates which domains the server receives requests from
Access-Control-Allow-Credentials no Whether it is possible to send cookies to the server, the default is false
Access-Control-Expose-Headers no Response headers that can be additionally exposed to the request

Among them, only Access-Control-Allow-Origin is required, and the value of the response header can be the value of the Origin of the request, or *, indicating that the server receives requests from all origins.

When a browser initiates a CORS request, it can only get 6 response header values ​​by default:

Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma

If you need to return other response headers to the front end, you can specify them in Access-Control-Expose-Headers .

Two request types for CORS

CORS has two types of requests, namely: simple request and not-so-simple request

As long as the following two conditions are met at the same time, it is a simple request.

(1) The request method is one of the following three methods:

  • HEAD
  • GET
  • POST

(2) HTTP header information does not exceed the following fields:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type: limited to three values ​​application/x-www-form-urlencoded , multipart/form-data , text/plain

If the above two conditions are not met at the same time, it is a non-simple request, and the browser handles these two kinds of requests differently.

Why are there two different types of requests?

The CORS specification requires that for HTTP request methods that may have side effects on server data (especially HTTP requests other than GET , or POST requests with certain MIME types ), the browser must first use the OPTIONS method to initiate a preflight request (preflight request). request), so as to know whether the server allows the cross-domain request.

After the server confirms the permission, the browser can initiate the actual HTTP request. In the return of the preflight request, the server can also notify the client whether it needs to carry identity credentials (including Cookies and HTTP authentication-related data).

Non-simple requests require the browser to send a preflight request first, and then send the actual request after the preflight is passed.

How to implement CORS

After knowing the implementation mechanism of CORS, we can solve the problems of CORS encountered.

1. Via JSONP

By exploiting the vulnerability that the <script> tag does not have cross-domain restrictions, web pages can obtain JSON data dynamically generated from other sources. JSONP requests must be supported by the other party's server.

JSONP is the same as AJAX, both are ways for the client to send requests to the server and get data from the server. But AJAX belongs to the same-origin policy, and JSONP belongs to the non-same-origin policy (supports cross-domain requests). The advantage of JSONP is that it is simple and compatible, and can be used to solve the problem of cross-domain data access by mainstream browsers. The disadvantage is that only supporting the GET method has limitations, insecure and may suffer from XSS attacks.

2. Use a reverse proxy server

The same-origin policy is a standard that browsers need to follow, and if it is a server-to-server request, it does not need to follow the same-origin policy

Therefore, the cross-domain problem can be effectively solved through a reverse proxy server. The proxy server needs to do the following steps:

1. Accept the client's request

2. Forward the request to the actual server

3. Return the server's response to the client

Nginx is a similar reverse proxy server, which can solve cross-domain problems by configuring Nginx proxy.

3. The server supports CORS

It is safest for the server to set which sources of requests are allowed, that is, after the server receives the request, it sets the Access-Control-Allow-Origin response header for the allowed request sources.

Annotated with @CrossOrigin

Taking Spring Boot as an example, you can specify which classes or methods support spanning through the @CrossOrigin annotation, as shown in the following code:

/**
 * 在类上加注解
 */
@CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
@RestController
public class UserController {
    
}
@RestController
public class UserController {
    @Resource
    private UserFacade userFacade;
    /**
     * 在方法上加注解
     */
    @GetMapping(ApiConstant.Urls.GET_USER_INFO)
    @CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
    public PojoResult<UserDTO> getUserInfo() {
        return userFacade.getUserInfo();
    }
}

Set global cross-domain configuration via CorsRegistry

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
            .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
	}
}

If you are using Spring Boot, the recommended practice is to define only one WebMvcConfigurer bean:

@Configuration
public class MyConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                    .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
            }
        };
    }
}

The above two methods work fine when no interceptor (Interceptor) is defined, but if you have a global interceptor to detect the user's login status, such as the following simple code:

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 检查是否登录
        if (token == null) {
            throw new InvalidTokenException(ResultCode.INVALID_TOKEN.getCode(), "登录态失效,请重新登录");
        }
        return true;
    }
}

When the custom interceptor returns true, everything works fine, but when the interceptor throws an exception (or returns false), subsequent CORS settings will not take effect.

Why does CORS not take effect when the interceptor throws an exception? Take a look at this issue:

when interceptor preHandler throw exception, the cors is broken

Someone submitted an issue stating that if an exception is thrown in the preHandler method of the custom interceptor, the global CORS configuration set by CorsRegistry will be invalid, but the members of Spring Boot do not think this is a bug.

The submitter then gave a specific example:

He first defined CorsRegistry and added a custom interceptor that threw an exception in the interceptor

cors-registry-1

Then he found that when AbstractHandlerMapping added CorsInterceptor, it added Cors interceptor at the end of the interceptor chain:

cors-registry-2

That will cause the problem mentioned above. After the exception is thrown in the custom interceptor, the CorsInterceptor interceptor has no chance to set the CORS-related response header in the response.

The submitter of the issue also gave a solution, which is to add the interceptor CorsInterceptor used to process Cors to the first position of the interceptor chain:

cors-registry-3

In this case, after the request comes, the first one will set the corresponding CORS response header for the response, and if other custom interceptors throw exceptions, it will not affect it.

It feels like a feasible solution, but the members of Spring Boot think that this is not a Spring Boot bug, but a Spring Framework bug, so the issue is closed.

Set global cross-domain configuration via CorsFilter

Since there is a problem with setting the global cross-domain configuration through the interceptor, we have another solution, which is set by the filter CorsFilter. The code is as follows:

@Configuration
public class MyConfiguration {
	@Bean
	public FilterRegistrationBean corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("http://127.0.0.1:9528");
        config.addAllowedOrigin("http://localhost:9528");
		config.addAllowedHeader("*");
		config.addAllowedMethod("*");
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}
}

Why do filters work but interceptors don't?

Because the filter depends on the servlet container, it can filter almost all requests based on function callbacks. The interceptor is dependent on the Web framework (such as the Spring MVC framework) and is implemented through AOP based on reflection.

The trigger sequence is shown in the following figure:

filter-interceptor-order

Because the filter is triggered before the interceptor, but if there are multiple filters, you also need to set CorsFilter as the first filter.

references

Browser Same Origin Policy

Browser Same Origin Policy and How to Avoid It

HTTP Access Control (CORS)

Cross-domain resource sharing CORS detailed explanation

How to prevent CSRF attacks?

Code by code, focusing on original sharing, describing the source code and principles with easy-to-understand graphics

CodeChaaaser

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324124005&siteId=291194637