SRI (Subresource Integrity) 是一個透過瀏覽器驗證引用的第三方資源,確保內容沒有被串改。

例如,我們常會引用第三方的 CDN 來源,以加快請求的時間。但由於 CDN 來源分散,難以進行定位,若其中一個來源節點受到劫持,可能會發生隨機的劫持,並且難以重複呈現,用戶刷新頁面後,就無法再重現。

因此,可透過 SRI 的方式,是在 script 或 link 增加 integrity 屬性,當瀏覽器讀取到 integrity 屬性,就會進行 SRI 驗證。

integrity 主要分成兩部分,hash演算法 以及 經過 bash64編碼的hash值,兩者透過 “-” 來連結,例如:

另外,透過 crossorigin 來聲明可以跨域

<script type="text/javascript" src="//cdnurl.xxx.js" 
integrity="sha256-xxxx sha384-xxxxx"
onerror="loadScriptError.call(this, event)"
crossorigin="anonymous"></script>

當發生錯誤時,重新載入本地資源

(function () {
    function loadScriptError (event) {
        // 上报
        ...
        // 重新加载 js
        return new Promise(function (resolve, reject) {
            var script = document.createElement('script')
            script.src = this.src.replace(/\/\/11.src.cn/, 'https://x.y.z') // 替换 cdn 地址为静态文件服务器地址
            script.onload = resolve
            script.onerror = reject
            script.crossOrigin = 'anonymous'
            document.getElementsByTagName('head')[0].appendChild(script)
        })
    }
    function loadScriptSuccess () {
        // 上报
        ...
    }
    window.loadScriptError = loadScriptError
    window.loadScriptSuccess = loadScriptSuccess
})()

使用 webpack

使用 Webpack 可以透過以下方式來生成 SRI

import SriPlugin from 'webpack-subresource-integrity'

const compiler = webpack({
    output: {
        crossOriginLoading: 'anonymous',
    },
    plugins: [
        new SriPlugin({
            hashFuncNames: ['sha256', 'sha384'],
            enabled: process.env.NODE_ENV === 'production',
        })
    ]
})

webpack 注入 onerror

const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')

module.exports = {
  //...
  plugins: [
    new HtmlWebpackPlugin(),
    new SriPlugin({
      hashFuncNames: ['sha256', 'sha384']
    }),
    new ScriptExtHtmlWebpackPlugin({
      custom: {
        test: /\/*_[A-Za-z0-9]{8}.js/,
        attribute: 'onerror',
        value: 'loadScriptError.call(this, event)'
      }
    }),
    new ScriptExtHtmlWebpackPlugin({
      custom: {
        test: /\/*_[A-Za-z0-9]{8}.js/,
        attribute: 'onsuccess',
        value: 'loadScriptSuccess.call(this, event)'
      }
    })
  ]
}

參考 https://zhuanlan.zhihu.com/p/69491352