The same-origin policy

Without the same origin policy, browsers are vulnerable to XSS and CSRF attacks. The so-called same origin means that the protocol, domain name, and port are the same. Even if two different domain names point to the same IP address, they are not of the same originThe same origin policy has the following restrictions:

  • Cookie, LocalStorage, IndexedDB and other stored content
  • DOM node
  • After the AJAX request is sent, the result is intercepted by the browser

There are three tags that allow cross-domain loading of resources: IMG, link, and script

If a protocol, subdomain name, main domain name, or port number is different, the domain name is counted as different. Requests for resources between different domains are “cross-domain”

Two points in particular:

1. If a protocol or port causes a cross-domain problem, the foreground cannot help it. 2. In terms of cross-domain issues, it is only identified by “the head of the URL” rather than whether the CORRESPONDING IP address of the domain name is the same. “URL header” can be interpreted as “protocol, domain name, and port must match”.

  • So the request is cross-domain, so is the request sent at all?

Cross-domain does not mean that the request cannot be sent out, the request can be sent out, the server can receive the request and return the result normally, but the result is blocked by the browser

Forms can make cross-domain requests, so why not Ajax? Because cross-domain is meant to prevent the user from reading content under another domain name, Ajax can get a response that the browser considers unsafe, so it blocks the response. But the form doesn’t get new content, so it can make a cross-domain request. It also shows that cross-domain does not prevent CSRF completely, because the request is made after all

Cross-domain solutions

JSONP

Realize the principle of

With Script, there are no cross-domain restrictions, and web pages can get JSON data dynamically generated from other sources. JSONP requests must be supported by the other party’s server

And AJAX contrast

JSONP, like AJAX, is a way for a client to send a request to the server and get data from the server. But AJAX is a same-origin policy, JSONP is a non-same-origin policy (cross-domain request)

The advantages and disadvantages

  • Advantages: Simple and compatible, it can be used to solve the cross-domain data access problem of mainstream browsers
  • Disadvantages: Limited support for only get methods, insecure and vulnerable to XSS attacks

The implementation process

  • A callback function whose name (such as show) is passed to the server that requests data across domains and whose parameter is to fetch the target data (the data returned by the server) is agreed with the server.
  • To create a<script>Tag, which assigns the cross-domain API data interface address to script SRC, and passes the function name to the server at that address. The callback = show)
  • Once the server receives the request, it needs to do something special: concatenate the name of the function passed in with the data it needs to give you into a string, for example: the name of the function passed in is show, and the data it prepares is show(‘ I don’t love you ‘).
  • Finally, the server returns the data to the client through HTTP protocol, and the client calls the callback function (show) previously declared to operate on the returned data

Or another process resolution

  1. The front end defines parsing functions (for example, jsonpCallback=function(){…. })
  2. Wrap request parameters in params form and declare execution functions (such as cb=jsonpCallback)
  3. The back end takes the execution function declared by the front end (jsonpCallback) and passes it to the front end by taking arguments and calling the execution function.
Const Koa = require(' Koa '); const app = new Koa(); const items = [{ id: 1, title: 'title1' }, { id: 2, title: 'title2' }] app.use(async (ctx, next) => { if (ctx.path === '/api/jsonp') { const { cb, id } = ctx.query; const title = items.find(item => item.id == id)['title'] ctx.body = `${cb}(${JSON.stringify({title})})`; return; } }) console.log('listen 8080... ') app.listen(8080); // Then execute in jsonp: node server.js // listen 8080...Copy the code
// The simplest way to implement the front end is to write a script and send a request:  //index.html <script type='text/javascript'> window.jsonpCallback = function (res) { console.log(res) } </script>// The first script creates a jsonpCallback function. But it has not been the < script SRC = "http://localhost:8080/api/jsonp? Id =1&cb=jsonpCallback' type='text/javascript'></script> And wait for the requested content to return // and then there is a local file server.js that uses Node to provide a service that emulates the server. // Define an interface/API /jsonp to query the data corresponding to the id. // When index. HTML is opened, the script tag is loaded and the cross-domain request is executedCopy the code

The whole process is as follows:

  • When the second script is executed, the id and cb parameters are placed in the URL because port 8080 is requested. And then the background can get these two parameters in the URL
  • Const {id, cb} = ctx.query in the backend code.
  • Once these two parameters are in hand, the back end might do some queries based on the ID
  • The second parameter, cb, gets “jsonpCallback”, which tells the back end that there will be a function called jsonpCallback to receive the data that the back end wants to return, and that the back end will write jsonpCallback() in the return body.
  • JsonpCallback ({“title”:”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}} So the browser console will print {title: ‘title1’}
// During development, you may encounter multiple JSONP requests with the same callback function name. In this case, you need to wrap a JSONP function. // index.html function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { ... params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src  = `${url}? ${arrs.join('&')}` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: 'Iloveyou' }, callback: 'show' }).then(data => { console.log(data) })Copy the code

The above is equivalent to http://localhost:3000/say? Wd =Iloveyou&callback=show, return show(‘ I don’t loveyou ‘), and print ‘I don’t loveyou’

// server.js let express = require('express') let app = express() app.get('/say', function(req, res) { let { wd, Log (wd) // Iloveyou console.log(callback) // show res.end(' ${callback}(' I don't loveyou ') ')}) app.listen(3000)Copy the code

Jsonp form of jQuery

JSONP is all GET and asynchronous requests, there is no other way to request or synchronous requests, and jQuery clears the cache for JSONP requests by default

$.ajax({ url:"http://crossdomain.com/jsonServerResponse", dataType:"jsonp", Type :"get",// omit jsonpCallback:"show",// omit jsonp:"callback",// omit jsonp:"callback" Function (data){console.log(data); }});Copy the code

cors

CORS supports all types of HTTP requests and is the fundamental solution for cross-domain HTTP requests

concept

Cross-domain resource sharing CORS: Supports both the browser and the backend: The browser automatically implements CORS communication, and the backend is the key to cORS communication. As long as the backend implements CORS, cross-domain is achieved

To use cross-domain resource sharing, the browser must support it and the server must agree to the “cross-domain” request. So the key to CORS implementation is that the server needs the server. There are usually the following configurations:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Max-Age

Setting CORS has nothing to do with the front end, but when you solve cross-domain problems in this way, there are two types of requests that can be sent: simple and complex, right

A simple request

CORS precheck request is not triggered: it is a simple request if the following conditions are met

  1. Use one of the following methods: GET, HEAD, POST
  2. Content-type values are limited to one of the following :(for example, application/json is a non-simple request)
  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

Xmlhttprequest.upload attribute access. 3. Set the request header outside the following collection

  • Accept
  • Accept-Language
  • Content-Language
  • Content-type (Additional restrictions need to be noted)
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width
  1. Any XMLHttpRequestUpload object in the request is not registered with any event listeners, and the XMLHttpRequestUpload object can use it
  2. No ReadableStream object is used in the request
// Create a folder cors in the root directory and add an index. HTML file to it: /cors/index.html <button id="getName"> getName </button> <script SRC = "https://cdn.bootcss.com/axios/0.19.2/axios.min.js" > < / script > < script > getName. The onclick = () = > {/ / a simple request Axios. Get (" http://127.0.0.1:8080/api/corsname "); } </script>Copy the code

For the convenience of debugging, using Node to write a simple front-end local service and back-end local service.

// create a new client.js file in the root directory and write:./client.js: const Koa = require(' Koa '); const fs = require('fs'); const app = new Koa(); app.use(async (ctx) => { if (ctx.method === 'GET' && ctx.path === '/') { ctx.body = fs.readFileSync('./index.html').toString(); } if (ctx.method === 'GET' && ctx.path === '/cors') { ctx.body = fs.readFileSync('./cors/index.html').toString(); } }) console.log('client 8000... ') app.listen(8000);Copy the code

There is an A tag in index. HTML

// create a new server.js file in the root directory and write:./server.js: const Koa = require(' Koa '); const app = new Koa(); app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", ctx.header.origin); // ctx.set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); // ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS"); ctx.set( "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Access, cc" ) if (ctx.method === 'OPTIONS') { ctx.status = 204; return; } await next(); if (ctx.path === '/api/corsname') { ctx.body = { data: 'LinDaiDai' } return; } }) console.log('server 8080... ') app.listen(8080);Copy the code
Package. json: {"scripts": {"client": "node./client.js", "server": "node./server.js"}}Copy the code

Then start NPM Run Client and NPM Run Server respectively

Open 127.0.0.1:8000/cors (or open 127.0.0.1:8000 and click the “A” TAB of Cors) and click the “Get Name” button

Complex request

The CORS request of complex request will add an HTTP query request, called “precheck” request, before formal communication. The request must be sent to the server by the OPTIONS method to know whether the server allows cross-domain request and the use of “precheck request”. Cross-domain requests can be avoided from having an unexpected impact on the server’s user data

The process goes something like this:

  • The browser sends a request to the server with the OPTIONS method, which carries the following header fields:
    • Access-control-request-method: specifies the Method used in the actual Request
    • Access-control-request-headers: Specifies the header fields carried by the actual Request
  • If the server accepts a subsequent request, the response body of this pre-request will carry the following fields:
    • Access-control-allow-methods: indicates the Methods allowed by the server
    • Access-control-allow-origin: specifies the domain name allowed to Access by the server
    • Access-control-allow-headers: specifies the header field allowed by the server
    • Access-control-max-age: the validity period (s) of the response, within which the browser does not need to send a precheck request for the same request

Access-control-max-age the browser itself maintains a maximum validity period. If the value of the header field exceeds the maximum validity period, it will not take effect, but will be dominated by the maximum validity period.

  • After the precheck request is complete, send the actual request

For example, put: Perform the following configuration in the background:

Res.setheader (' access-control-allow-methods ', 'PUT') res.setheader (' access-control-max-age ', If (req.method === 'OPTIONS') {res.end()} app.put('/getData', function(req, Res) {console.log(req.headers) res.end(' request not allowed ')})Copy the code

Complete complex request examples, and describe the CORS request related fields

// let XHR = new XMLHttpRequest() document.cookie = 'name=xiamen' // Cookies cannot cross domain xhr.withcredentials = true // The front set whether or not to bring a cookie XHR. Open (' PUT ', 'http://localhost:4000/getData', true) XHR. SetRequestHeader (' name ', 'xiamen') xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) | | XHR. Status = = = 304) {the console. The log (XHR. Response) / / get the response headers, Access-control-expose-headers console.log(xhr.getresponseheader ('name'))}}} xhr.send()Copy the code
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
Copy the code
Server2.js let express = require('express') let app = express() let whitList = ['http://localhost:3000'] // Set whitelist app.use(function(req, res, Next) {let origin = req.headers. Origin if (whitlist.includes (origin)) {// Set which source can access me Res. setHeader(' access-Control-allow-headers ', Origin) Res.setheader (' access-control-allow-methods ', 'PUT') // Cookie res.setHeader(' access-Control-allow-credentials ', Res.setheader (' access-Control-max-age ', 6) res.setheader (' access-Control-expose-headers ', 6) res.setheader (' access-Control-expose ', 6) 'name') if (req.method === 'OPTIONS') {res.end()}} next()}) app.put('/getData', function(req, Res) {console.log(req.headers) res.setheader ('name', 'jw') Res.end (' I don't love you ')}) app.get('/getData', function(req, Res) {console.log(req.headers) res.end(' I don't love you ')}) app.use(express.static(__dirname)) app.listen(4000)Copy the code

The above code from http://localhost:3000/index.html to http://localhost:4000/ cross-domain request backend is the key to the realization of CORS communication

Solution in Node

  • Native: back-end solutions. Node CORS resolution code
   app.use(async (ctx, next) => {
        ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
        ctx.set("Access-Control-Allow-Credentials", true);
        ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
        ctx.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, cc");
        if (ctx.method === "OPTIONS") {
            ctx.status = 204;
            return;
        }
        await next();
    });
Copy the code
  • Third party middleware: Middleware can also be used directly for convenience
const cors = require("koa-cors");
app.use(cors());
Copy the code

Cookie about CORS

To pass cookies, you need to meet three criteria

  1. Set the withCredentials in web requests

By default, browsers do not use cookies for cross-domain requests. However, we can pass cookies by setting withCredentials.

Var XHR = new XMLHttpRequest(); xhr.withCredentials = true; / / axios set mode axios. Defaults. WithCredentials = true;Copy the code
  1. Access – Control – Allow – Credentials to true
  2. Access – Control – Allow – Origin to a *

describe

Simple answer: When we make a cross-domain request, if it is not a simple request, the browser will automatically trigger the pre-check request, also known as the OPTIONS request, to confirm whether the target resource supports cross-domain. If the request is simple, the precheck is not triggered and the request is normal

Detailed answer:

  • The browser first matches the front-end and background IP addresses based on the same origin policy. If the IP addresses are the same origin, the browser directly sends a data request. If the source is different, a cross-domain request is sent.
  • After receiving the cross-domain request from the browser, the server returns the corresponding file header based on its own configuration. If no cross-domain permission has been configured, the file header does not contain the access-Control-allow-origin field. If the domain name has been configured, the access-Control-allow-origin + domain name in the corresponding configuration rule is displayed.
  • The browser matches the access-Control-Allow-Origin field in the received response header. If the field is not present, cross-domain is not allowed and an error is thrown. If the field exists, the system compares the field content with the current domain name. If the field is of the same origin, the system can cross domains, and the browser accepts the response. If the source is different, the domain name is not cross-domain and the browser does not accept the response and throws an error

summary

  1. In the new Chrome, if you send complex requests, you don’t see options requests. Here you can set Chrome ://flags/# out-of-of-blink-cors to disbale and restart the browser. Options requests are visible for non-simple requests.
  2. This cross-domain header is not normally enabled by back-end interfaces, except for less important interfaces that are irrelevant to the user

Node middleware proxy (cross domain twice)

Realize the principle of

The same origin policy is a standard that the browser must follow. However, if the server requests the same origin policy to the server, the proxy server does not need to follow the same origin policy. The following steps are required:

  • Accept client requests
  • Forwards the request to the server
  • Get the server response data
  • The response is forwarded to the client

Agents in cli tools

Webpack (4.x)

Proxy can be configured in Webpack to quickly gain interface proxy capabilities.

  const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    module.exports = {
        entry: {
            index: "./index.js"
        },
        output: {
            filename: "bundle.js",
            path: path.resolve(__dirname, "dist")
        },
        devServer: {
            port: 8000,
            proxy: {
                "/api": {
                    target: "http://localhost:8080"
                }
            }
        },
        plugins: [new HtmlWebpackPlugin({
            filename: "index.html",
            template: "webpack.html"
        })]
    };
Copy the code
<button id=" getList "> </button> <button id="login"> </button> <script SRC = "https://cdn.bootcss.com/axios/0.19.2/axios.min.js" > < / script > < script > axios. Defaults. The withCredentials = true; getlist.onclick = () => { axios.get("/api/corslist").then(res => { console.log(res.data); }); }; login.onclick = () => { axios.post("/api/login"); }; </script>Copy the code
Vue-cli 2.x
// config/index.js
...
proxyTable: {
  '/api': {
     target: 'http://localhost:8080',
  }
},
...
Copy the code
Vue-cli 3.x
Module. exports = {devServer: {port: 8000, proxy: {"/ API ": {target: "http://localhost:8080" } } } };Copy the code
Proxy (2.x)
{
  "/api": {
    "target": "http://localhost:8080"
  }
}
Copy the code

Use your own proxy tool

cors-anywhere

  • The service side
/ / Listen on a specific host via the host environment variable var host = process. The env. The host | | "0.0.0.0"; // Listen on a specific port via the PORT environment variable var port = process.env.PORT || 7777; var cors_proxy = require("cors-anywhere"); cors_proxy .createServer({ originWhitelist: [], // Allow all origins requireHeader: ["origin", "x-requested-with"], removeHeaders: ["cookie", "cookie2"] }) .listen(port, host, function() { console.log("Running CORS Anywhere on " + host + ":" + port); });Copy the code
  • The front end
< script SRC = "https://cdn.bootcss.com/axios/0.19.2/axios.min.js" > < / script > < script > axios. Defaults. WithCredentials = true; Getlist. Onclick = () = > {axios. Get (" http://127.0.0.1:7777/http://127.0.0.1:8080/api/corslist "). Then (res = > { console.log(res.data); }); }; The login. The onclick = () = > {axios. Post (" http://127.0.0.1:7777/http://127.0.0.1:8080/api/login "); }; </script>Copy the code

Disadvantages: Cookie delivery is not possible, because this is a proxy library, the author thinks it is too insecure to transfer cookies in the middle. Github.com/Rob – W/cors…

charles

  • Introduction and use of Charles

Using Charles to cross domains is essentially interception and proxy of requests

Set the proxy in tools/ Map remote

  • The front-end code
<button id=" getList "> </button> <button id="login"> </button> <script SRC = "https://cdn.bootcss.com/axios/0.19.2/axios.min.js" > < / script > < script > axios. Defaults. The withCredentials = true; getlist.onclick = () => { axios.get("/api/corslist").then(res => { console.log(res.data); }); }; login.onclick = () => { axios.post("/api/login"); }; </script>Copy the code
  • The back-end code
Router. Get ("/ API /corslist", async CTX => {CTX. Body = {data: [{name: ""}]}; }); router.post("/api/login", async ctx => { ctx.cookies.set("token", token, { expires: new Date(+new Date() + 1000 * 60 * 60 * 24 * 7) }); Ctx. body = {MSG: "success ", code: 0}; });Copy the code

For example, a local file named index. HTML requests data from the proxy server http://localhost:3000 to the target server http://localhost:4000

/ / index. HTML (http://127.0.0.1:5500) < script SRC = "https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js" > < / script > < script >  $.ajax({ url: 'http://localhost:3000', type: 'post', data: { name: 'xiamen', password: '123456' }, contentType: 'application/json; charset=utf-8', success: function(result) { console.log(result) // {"title":"fontend","password":"123456"} }, error: function(msg) { console.log(msg) } }) </script>Copy the code
// server1.js proxy server (http://localhost:3000) const HTTP = require(' HTTP ') Const server = http.createserver ((request, response) => {// Proxy server, WriteHead (200, {' access-control-allow-origin ': '*', 'access-control-allow-methods ': '*', 'access-Control-allow-headers ':' content-type '}) const proxyRequest = http. request({host: '127.0.0.1', port: 4000, URL: '/', method: request.method, headers: Request. Headers}, serverResponse => {// Var body = 'serverResponse.on('data', chunk => {body += chunk}) serverResponse.on('end', () => {console.log('The data is' + body) Forward the response to the browser response.end(body)})}).end()}) server.listen(3000, () => { console.log('The proxyServer is running at http://localhost:3000') })Copy the code
// server2.js(http://localhost:4000)
const http = require('http')
const data = { title: 'fontend', password: '123456' }
const server = http.createServer((request, response) => {
  if (request.url === '/') {
    response.end(JSON.stringify(data))
  }
})
server.listen(4000, () => {
  console.log('The server is running at http://localhost:4000')
})
Copy the code

{“title”:”fontend”,”password”:”123456″}}

Nginx reverse proxy

  • The reverse proxy

We send the request to the server, and the server forwards our request, and all we need to do is communicate with the proxy server

  • Forward agent

For the target server, the real client is not felt, and it communicates with the proxy client, such as Science Google software is a forward proxy To summarize: A server agent is called a reverse proxy, and a client agent is called a forward proxy

  • Both the Node middleware proxy and the Nginx reverse proxy have no restrictions on the server through the same origin policy
  • In daily work, the cross-domain solutions used more are CORS and NGINX reverse proxies
  • The nginx implementation works like a Node middleware proxy, requiring you to build a forwarding Nginx server to forward requests
  • Nginx installation tutorial

Nginx reverse proxy is the simplest way to cross domains. Nginx configuration can be modified to resolve cross-domain issues, support all browsers, support sessions, do not need to change any code, and do not affect server performance

Implementation approach

Through nginx configure a proxy server (domain name and domain1 the same, different port) as a jumper, reverse proxy access to domain2 interface, and can incidentally modify the cookie domain information, convenient cookie writing in the current domain, to achieve cross-domain login

To download nginx then nginx directory nginx. Conf modified as follows:

// Proxy server {listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; # reverse proxy proxy_cookie_domain www.domain2.com www.domain1.com; # change cookie domain name index index.html index.htm; # When accessing Nignx with middleware proxy interface such as Webpack-dev-server, there is no browser participation, so there is no source restriction. Add_header access-Control-allow-origin http://www.domain1.com; * add_header access-control-allow-credentials true; * add_header access-control-allow-credentials true; }}Copy the code

Nginx: sudo nginx: sudo nginx -s reload

// index.html var xhr = new XMLHttpRequest(); // Whether the browser reads and writes cookies xhr.withCredentials = true; / / access proxy server in nginx XHR. Open (' get 'and' http://www.domain1.com:81/?user=admin ', true); xhr.send();Copy the code
// server.js var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // Write cookie res.writeHead(200, {' set-cookie ': 'l=a123456; Path=/; Domain=www.domain2.com; HttpOnly' // HttpOnly: script cannot read}); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080... ');Copy the code

postMessage

websocket

Websocket is a persistent protocol of HTML5, which realizes the full duplex communication between browser and server, and is also a cross-domain solution. WebSocket and HTTP are both application layer protocols based on TCP. However, WebSocket is a two-way communication protocol. After the connection is established, both the WebSocket server and client can actively send or receive data to each other. At the same time, the WebSocket needs to use HTTP protocol to establish a connection. After the connection is established, the two-way communication between the client and server is independent of HTTP

window.name + iframe

location.hash + iframe

document.domain + iframe

Learn from reading

  • Nine cross-domain implementation principles
  • 10 cross-domain solutions
  • Nginx concepts and configuration
  • CORS principle and implementation