CORS is a W3C standard, whose full name is called "cross origin resource sharing"; Before introducing CORS in detail, first briefly introduce what is the homology policy, so as to understand the origin and necessity of CORS.
Browser homology policy
"Homology policy" is the cornerstone of browser security. At present, all browsers implement this policy.
meaning
The so-called "homology" refers to the following three same:
- Same agreement
- Same domain name
- Same port
for instance:
Current web address | Requested page address | Cross domain (different sources) | reason |
---|---|---|---|
http://www.wu-yikun.top/page.html | http://www.wu-yikun.top/main.html | no | Same protocol (http), same domain name (www.wu-yikun.top), same port (80) |
http://www.wu-yikun.top/page.html | https://www.wu-yikun.top/other.html | yes | Different protocols (http and https) |
http://www.wu-yikun.top/page.html | http://www.wu-yikun.com/page.html | yes | Different domain names (www.wu-yikun.top and www.wu-yikun.com) |
http://www.wu-yikun.top/page.html | http://www.wu-yikun.top:8090/other.html | yes | Different ports (80 and 8090) |
Now let's look at this picture. Is it clear at a glance?
objective
Excerpt from teacher Ruan Yifeng's article
The purpose of homology policy is to ensure the security of user information and prevent malicious websites from stealing data.
Imagine this situation: website A is A bank. After users log in, they browse other websites. What happens if other websites can read the cookies of website A?
Obviously, if the Cookie contains privacy (such as total deposits), this information will be leaked. What's more terrible is that cookies are often used to save the login status of users. If users don't log out, other websites can impersonate users and do whatever they want. Because the browser also stipulates that submitting forms is not limited by the homology policy.
It can be seen that the "homology policy" is necessary, otherwise cookies can be shared, and the Internet will have no security at all.
limit
With the development of the Internet, the homology policy is becoming more and more strict. If it is not homologous, the following behaviors will be limited.
- Cookie s, LocalStorage and IndexDB cannot be read
- DOM cannot get
- AJAX request could not be sent
Just because an AJAX request cannot be sent can curb many reasonable uses.
Next, it introduces how to realize cross domain resource sharing to avoid homology policy.
CORS details
brief introduction
CORS requires both browser and server support. At present, all browsers support this function. In the process of CORS communication, the browser will complete automatically without user participation. Therefore, in order to realize CORS communication, the key is to make the server support CORS interface and cross source communication.
CORS two types of requests
The browser divides CORS requests into two categories: simple requests and non simple requests.
Simple request
As long as the following two conditions are met at the same time, it is a simple request:
- The request method is one of three types
- HEAD
- GET
- POST
- The HTTP header information does not exceed the following fields
- Accept
- Accept-Language
- Content-Language
- Content type: only limited to one of application/x-www-form-urlencoded, multipart / form data and text/plain
If the above two conditions are not met at the same time, it is a non simple request.
Here is a simple CORS request:
The following is the request message sent by the browser to the server:
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-alive Origin: https://foo.example
The request header field Origin indicates that the request originated from http://foo.example , take another look at the response message:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2021 00:23:53 GMT Server: Apache/2 Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml [XML Data]
In this example, the access control allow origin returned by the server: * indicates that the resource can be accessed by any foreign domain.
Access-Control-Allow-Origin: *
use Origin and Access-Control-Allow-Origin Can complete the simplest access control. If the server only allows from https://foo.example The contents of the header field are as follows:
Access-Control-Allow-Origin: https://foo.example
Non simple request
Non simple requests are those that have special requirements for the server. For example, the request method is PUT or DELETE, or the content type field type is application/json.
A major feature of non simple CORS requests is that an HTTP query request will be added before formal communication, which is called "Pre check" request (Preflight request). The method of the "pre check" request is OPTIONS. The use of the "pre check" request can avoid the unexpected impact of cross domain requests on the server's user data.
The following is an HTTP request that needs to perform a pre check request:
As described below, the actual POST request does not carry the access control request - * header. They are only applicable to OPTIONS requests.
The following is the complete information interaction between the server and the client.
The first is the pre inspection request:
OPTIONS /doc HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-alive Origin: https://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type
The browser has detected that requests originating from JavaScript need to be pre checked. From the above message, we can see that lines 1 to 10 send a "pre inspection request" using the OPTIONS method. OPTIONS is a method defined in HTTP/1.1 protocol to obtain more information from the server. This method has no impact on server resources. The pre inspection request carries the following two header fields at the same time:
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Header field Access-Control-Request-Method Tell the server that the actual request will use the POST method.
Header field Access-Control-Request-Headers Inform the server that the actual request will carry two custom request header fields: X-PINGOTHER and content type.
Based on this, the server determines whether the actual request is allowed.
Pre inspection response:
HTTP/1.1 204 No Content Date: Mon, 01 Dec 2021 01:15:39 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Keep-Alive: timeout=2, max=100 Connection: Keep-Alive
(1) The server's response carries access control allow origin: https://foo.example To limit the source domain of the request.
(2) At the same time, the access control allow methods carried indicates that the server allows the client to initiate requests using POST and GET methods (compared with Allow The response header is similar, but it has strict access control).
(3) The header field access control allow headers indicates that the server allows the fields X-PINGOTHER and content type to be carried in the request. If the browser request includes an access control request headers field, the access control allow headers field is required. It is also a comma separated string, indicating all header information fields supported by the server, not limited to the fields requested by the browser in "pre check".
(4) Finally, the header field access control Max age indicates that the effective time of the response is 86400 seconds, that is, 24 hours. Within the effective time, the browser does not need to send a pre check request again for the same request. Note that the browser itself maintains a Maximum effective time , if the value of the header field exceeds the maximum effective time, it will not take effect.
After the pre inspection request is completed, send the actual request:
POST /doc HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-alive X-PINGOTHER: pingpong Content-Type: text/xml; charset=UTF-8 Referer: https://foo.example/examples/preflightInvocation.html Content-Length: 55 Origin: https://foo.example Pragma: no-cache Cache-Control: no-cache
Actual response message:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2021 01:15:40 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 235 Keep-Alive: timeout=2, max=99 Connection: Keep-Alive Content-Type: text/plain
In the header information above, the access control allow origin field must be included in each response.
Spring Boot solves cross domain problems
After talking so much, let's feel the actual scene.
Do you feel a certain "intimacy"? Let's solve this sense of powerlessness.
Manually set the response header through the Filter
import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component @Slf4j @WebFilter(urlPatterns = {"/*"}, filterName = "corsFilter") public class CorsFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("Start cross domain filter"); } @Override public void doFilter(ServletRequest request, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) resp; // Manually set response headers to resolve cross domain access response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE"); // Set expiration time response.setHeader("Access-Control-Max-Age", "86400"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, uuid"); // Support HTTP 1.1 response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // Support HTTP 1.0 response. setHeader("Expires", "0"); response.setHeader("Pragma", "no-cache"); // code response.setCharacterEncoding("UTF-8"); chain.doFilter(request, resp); } @Override public void destroy() { log.info("Destroy cross domain filters"); } }
Use @ CrossOrigin annotation (local cross domain)
@CrossOrigin annotation source code:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CrossOrigin { @AliasFor("origins") String[] value() default {}; @AliasFor("value") String[] origins() default {}; String[] allowedHeaders() default {}; String[] exposedHeaders() default {}; RequestMethod[] methods() default {}; String allowCredentials() default ""; long maxAge() default -1L; }
Use @ CrossOrigin annotation:
@CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 86400) @PostMapping("/login") public String login(@RequestBody User user) { TODO.. }
However, the source code annotated by @ CrossOrigin determines that it can only be cross domain configured for a single interface, that is, local cross domain. Although it is easier to use, such as the Filter filter on, it is obviously not what we want, and this annotation is rarely used in actual development.
Implement WebMvcConfigurer interface
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // Indicates which domains are allowed to access. The simple point can be* .allowedOrigins("http://localhost:3000") .allowedHeaders("*") .allowedMethods("*") // allowCredentials(true): indicates that identity credentials are attached // Once the allowCredentials(true) method is used, allowedOrigins("*") needs to specify a specific domain, not* .allowCredentials(true) .maxAge(86400); } }
The above method works normally when there is no Interceptor defined, but if you have a global Interceptor, such as the Interceptor that detects user login:
public class AuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // Fetch the token from the http request header String token = httpServletRequest.getHeader("token"); // Check whether you are logged in if (token == null) { throw new InvalidTokenException(ResultCode.INVALID_TOKEN.getCode(), "Login information has expired, please login again"); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
When the custom interceptor returns true, everything is normal, but when the interceptor throws an exception (or returns false), the subsequent CORS configuration will not take effect.
Why does the interceptor throw an exception and CORS doesn't take effect? Take a look at the issue proposed on GitHub:
when interceptor preHandler throw exception, the cors is broken #9595
The general contents are as follows:
Someone submitted an issue stating that if an exception is thrown in the preHandler method of the custom interceptor, the global CORS configuration set through CorsRegistry will fail, but the members of Spring Boot do not think it is a BUG ๐.
Then the author gives a specific example:
He first defined CorsRegistry and added a custom interceptor, which threw an exception:
Then it is found that AbstractHandlerMapping adds the interceptor of Cors to the end of the interceptor chain when adding CorsInterceptor:
That will cause the problem mentioned above: after throwing an exception in the custom interceptor, the CORS interceptor interceptor has no chance to set the CORS related response header in the response.
The issuer also gives a solution, that is, the interceptor corsinterpector used to process CORS is clamped at the first position of the interceptor chain:
In this way, once the request comes, the first interceptor will set the corresponding CORS response header for the response (e.g. access control allow origin, access control allow methods, access control allow headers). If other custom interceptors throw exceptions later, it will not have any impact!
I think this is a feasible solution, but members of Spring Boot believe that this is not a Bug of Spring Boot, but a Bug of Spring Framework, and all have closed this issue.
Inject CorsFilter filter
Using filters can avoid the conflict between interceptors and global cross domain configuration. The code is as follows:
@Configuration public class CorsFilterConfiguration { @Bean public CorsFilter corsFilter() { // Add configuration after creating the CorsConfiguration object CorsConfiguration corsConfiguration = new CorsConfiguration(); // Set which original fields to release corsConfiguration.addAllowedOrigin("*"); // Which original request header information is released corsConfiguration.addAllowedHeader("*"); // Which request methods are released corsConfiguration.addAllowedMethod("*"); // Add mapping path UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return new CorsFilter(source); } }
โญ Why can filters avoid conflicts and interceptors don't?
Because the filter depends on the Servlet container, it can filter almost all requests based on function callbacks. The interceptor relies on Web frameworks (such as Spring MVC framework) and is implemented through AOP based on reflection.
The trigger sequence is as follows:
Because the filter is triggered before the interceptor, but if there are multiple filters, you also need to set CorsFilter as the first filter.
Other perspectives | ideas for solving cross domain CORS
The server supports CORS
๐คจ What are you expecting?
The [Spring Boot solves cross domain problems] (#Spring Boot solves cross domain problems) just mentioned is to explain how to realize CORS cross domain resource sharing on the server.
The above four methods are effective! If in doubt, see in the comments area.
๐ If you use Spring framework instead of Spring Boot, I also found an official document for your reference: CORS support in Spring Framework
JSONP
Taking advantage of the vulnerability that the < script > tag does not have cross domain restrictions, web pages get JSON data dynamically generated from other sources across domains. JSONP requests must be supported by the server of the other party.
Note: three tags themselves allow resources to be loaded across domains.
- <img src="xxx">
- <link href="xxx">
- <script src="xxx">
JSONP is the same as Ajax, which is the way that the client sends a request to the server and obtains data from the server. However, AJAX belongs to the same source policy and JSONP belongs to the non same source policy (supporting cross domain requests).
Advantages: it is simple and has good compatibility. It can be used to solve the problem of cross domain data access of mainstream browsers.
Disadvantages: only GET method is supported, which has limitations, is unsafe, and may be attacked by XSS.
Reverse proxy server
The same origin policy is the standard that the browser needs to follow. If the server requests from the server, it does not need to follow the same origin policy. Therefore, the cross domain problem can be effectively solved through the reverse proxy server. The proxy server needs to do the following:
- Accept client requests
- Forward the request to the actual server
- Return the response result of the server to the client
Nginx is a similar reverse proxy server. It solves cross domain problems by configuring nginx proxy across domains.