天天看點

跨域問題_關于跨域問題

1. 為何會出現跨域問題?

官方: 浏覽器的同源政策, 同源政策限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行互動。這是一個用于隔離潛在惡意檔案的重要安全機制.

通俗點說, 我本人, 用了某人的東西, 可能會被打(或者發生其他事).

2. 沒有同源政策會發生啥事?

a. 接口請求可能會遭遇CSRF攻擊

    這得從一個cookie說起, 我們一般使用cookie來處理登入場景, 目的是為了讓服務端知道這次請求是誰發出的, 當我請求接口進行登入, 服務端通過驗證後會在響應頭加入Set-Cookie字段, 下次發請求時會自動把cookie放在http請求頭Cookie中, 這時候服務端就知道我已經登入過了. 那麼就會有以下場景:

    某天我登入某個網站M, 登入成功後就開始看東西, 此時有人給我發了一個連結A, 我出于好奇就點進去看. 這個時候, 沒有同源政策的話, A網站在接收我的頁面請求時, 就能拿到我目前網站M的cookie, 等于登入了我的網站, 然後為所欲為. 

    看到這裡, 又有人會說, cookie是明文, 有同源政策限制我還不是一樣為所欲為. 這裡就順便提一提, 服務端可以設定httpOnly來使得前端無法操作cookie, 或者設定secure保證https的加密通信傳輸防止被截獲

b. 網站DOM被查詢

// 設搞了個嵌套某支付網頁的釣魚網站// 沒人限制我拿某支付網站的DOM元素const fish = window.frames['pay']const targetInput = fish.document.getElementById('targetInput')console.log(`我拿到你目前輸入的元素${targetInput}, 就能知道你的密碼賬戶`)
           

    然後, 就可以開始釣魚了, 發個警報短信啥的給仇人, 說他支付賬戶有一筆大額消費記錄, 讓他趕緊點選www.play.com檢視, 仇人點開一看, 是這界面, 趕緊登入, 這樣就拿到了他的支付賬戶跟密碼, 開始一些惡性操作. (隻是舉個簡單的栗子, 不必認真)

綜上, 浏覽器的同源政策能規避一下基本的安全風險

3. 今日科學, 走進跨域

跨域問題_關于跨域問題

準備工作: 前端代碼知識, 搭建一個簡易的服務, 簡易的前端請求代碼, 還有一個求知的心

a.一個簡單的vue項目, App.vue

<template>  <div id="app">  div>template><script>export default {  name: 'App',  components: {    HelloWorld  },  created() {    this.$axios('http://localhost:8088/').then(res => {      console.log(res)    })  }}script>
           

b. 一個簡易的服務

const http = require('http')const allurl = require('url')const server = http.createServer()server.on('request', function (req, res) {  //接受用戶端請求  let parseObj = allurl.parse(req.url, true)// url.parse 參數轉成對象 true  let url = parseObj.pathname//單獨擷取?前面的url參數路徑  res.setHeader('Content-Type', 'application/json')  res.writeHead(200, 'ok')  if (url === '/') {    res.end(JSON.stringify({code:200}))  } else {    res.end('404 NOT FOUND', 'utf-8')  }  console.log('接受用戶端請求')  res.end('預設資料')})server.listen(8088, (req, res) => {  console.log('伺服器打開在: http://localhost:8088')})
           

如上代碼, 好家夥, 上來就是一個跨域報錯!

跨域問題_關于跨域問題
跨域問題_關于跨域問題

a. 設CORS

簡單粗暴的方法就是從服務端下手, 給他設定跨資源共享, 這樣前端也不用做什麼操作, 要白名單就跟開發服務的人說一下就完事了

// 第二個參數可設定為*或者用戶端通路白名單// *表示允許所有請求資源共享, 一般不推薦, 有安全隐患// 如果是本地html檔案, 設為'null'就好了res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');// 設定允許跨域請求的方式res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS");// 當有多個域名時可通過判斷來進行, 代碼如下server.on('request', function (req, res) {  xxxxx....  // 白名單清單  const allowList = ['http://localhost:8080', 'https://www.baidu.com']  // 含在白名單内的, 則可共享資源  if(allowList.includes(req.headers.origin) {    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);  }  xxxxx...})
           

b. JSONP(僅适用于get請求, 不推薦使用)

// ----前端代碼-----<template>  <div id="app">    <p>jsonp請求結果p>    <h2>h2>   div>template><script>export default {  name: 'App',  data() {    return {      // 設定一個回調函數用來接收請求傳回的東西      jsonpCallback: (res) => {        console.log('擷取資料成功')        let h2 = document.getElementsByTagName('h2')[0]        setTimeout(function () {            h2.innerHTML = res.message        }, 2000)      }    }  },  created() {    var script = document.createElement("script");    script.src="http://localhost:3000?callback=jsonpCallback";    document.getElementById('app').appendChild(script);  }}script>// ----------伺服器代碼-----------const http = require('http')const allurl = require('url')const server = http.createServer()server.on('request', function (req, res) {   // url.parse 參數轉成對象 true  let parseObj = allurl.parse(req.url, true)  res.setHeader('Content-Type', 'application/json')  res.writeHead(200, 'ok')  // 接收前端傳來的函數名稱,jsonpCallback  var callbackName = parseObj.query.callback;  console.log(callbackName)  // 将資料作為回調函數的參數一并傳回    res.end(callbackName+"({'message': 'JSONP!'});  ");  console.log('接受用戶端請求')  // res.end('預設資料')})server.listen(3000, (req, res) => {  console.log('伺服器打開在: http://localhost:3000')})
           

c. 使用代理

vue項目在開發環境中,可以通過配置proxy來解決跨域問題(僅在開發環境)

建立配置檔案vue.config.js

module.exports = {  devServer: {    port: 8080,    proxy: {      // 屬性名為代理名稱      '/proxy': {        // 目标服務的路徑        target: 'http://localhost:3000',        changeOrigin: true,        pathRewrite: {          '^/proxy': '/'        }      }    }  }}
           
// 從前端發請求即可this.$axios('/proxy/demo').then(res => {   let h2 = document.getElementsByTagName('h2')[0]   console.log(res)   h2.innerHTML = res.data;})
           

上面是架構的,下面我們來寫個簡單的代理

html頁面挂載在代理服務上,然後透過代理服務請求真正的服務

<html ><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Documenttitle>  <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js">script>head><body>  <button>通過proxy解決跨域問題button>  <script>    var btn = document.getElementsByTagName('button')[0];    btn.addEventListener('click', function () {        var proxy_url = 'http://localhost:8080/proxy/';        const res = $.ajax({          url: proxy_url,          async:false        })        console.log(res.responseText);    })script>body>html>
           
var http = require('http');var fs = require('fs');http.createServer(function (req, res) {    //加載靜态html檔案    if (req.url == "/") {      res.writeHead(200, { 'Content-Type': 'text/html' });      res.end(fs.readFileSync(__dirname + '/index.html'));    }    //判斷為代理請求時,由伺服器去發起請求再将對應資料傳回    else if(req.url.indexOf('/proxy') !== -1) {      let targetUri = req.url.replace('/proxy', '/')      http.get(`http://localhost:8088${targetUri}`, function(targetRes) {        targetRes && targetRes.on('data', function(data) {          res.writeHead(200, {'Content-Type': 'application/json'})          console.log(data)          res.end(data)        })      })    }}).listen(8080, function () {    console.log("http://localhost:8080");});
           

代理的方式有多種,這裡隻是做個簡單的展示,了解大概是怎麼一回事就可以了。

小結:

出現跨域問題是由于浏覽器的同源政策。而解決跨域問題可通過設CORS、jsonp或者代理,當然還有其他方式但目前還未嘗試過,以後有碰到再進行補充