项目中的跨域--回顾

193 阅读5分钟

一般前后端项目中跨域解决

  1. 开发环境中直接vite, webpack 正向代理, 他们内置了一个简单的node server
  2. 生产环境直接部署到和和后端接口相同的域名下
  3. 如果调用其他不同域名下的请求呢?
    • 传统jsonp,利用script的跨域, 常见的就是百度搜索联想词的使用,可以调用百度接口
    • nginx反向代理
    • bff 转发,也类似反向代理
    • cors 响应头设置,需要对方服务器配合

electron中跨域的处理

跨域是浏览器端的问题, 可以直接用node发起请求, 避免跨域问题. 当然也有一些弊端

  1. bug排查,需要打印大量日志排查, 不如Chrome中的网络控制台直观,中间多了一层那么排查的时候就多了一个流程
  2. 中间转发有性能损伤,其次需要考虑更多的情况,比如有个get请求,但参数直接写在了body上. 这种情况很多时候无法考虑周到
  3. 还有证书错误的问题, nodejs中需要自己去忽略证书错误,但浏览器正常

所以建议请求放在前端页面去发起, electron中有以下几种方式解决跨域

  1. 首先file协议不存在跨域
  2. 使用webPreferences禁用 Web 安全策略,但这种方式已经不推荐了
  3. 渲染进程请求ipc,主进程发起请求 ,也不推荐,繁琐
  4. session模块修改cors请求头. 推荐 chrome中也可以看到网络监控

源码如下


    const filter = {
        urls: [
            'https://主机名1.com/*',
            'https://主机名2.cn/*',
        ]
    }
    // session.defaultSession.webRequest.onBeforeSendHeaders((details,callback) => {
    //     details.requestHeaders['Origin'] = '*'
    //     callback({ requestHeaders:details.requestHeaders});
    // })
    // 之前看到有人这样写,其实cors只和响应有关,无需设置请求Origin,注意filter后要带*
    session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
        details.responseHeaders!['Access-Control-Allow-Origin'] = ['*']
        callback({
            cancel: false,
            responseHeaders: details.responseHeaders
        })
    })

但仅仅以上就可以了吗,还不够,跨域三剑客, origin,method,cookie都需要设置

  session.defaultSession.webRequest.onHeadersReceived(sessionFilter, (details, callback) => {
        // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
        details.responseHeaders!['Access-Control-Allow-Origin'] = ['http://localhost:8081']
        // 'http://localhost:8081',
        // The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
        details.responseHeaders!['Access-Control-Allow-Credentials'] = ['true']
        details.responseHeaders!['Access-Control-Allow-Methods'] = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']

        callback({
            cancel: false,
            responseHeaders: details.responseHeaders,
        })
    })

如果需要携带cookie, 请求的时候也要设置

fetch('https://主机名1.cn/api/v1/test',{credentials:'include'})

electron中设置cookie


// 跨域token的设置
// 方法一
  session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
        const token = xxx.token
        logger.info('cookies', details.requestHeaders['Cookie'])
        details.requestHeaders['Cookie'] = `token=${token}`
        callback({ requestHeaders: details.requestHeaders })
    })
  // 方法二
  export const setTokenToCookie = (cookie:string) =>{
    const parseCk = parseCookie(cookie)
    const maxAge = parseInt(parseCk['Max-Age'])
    const expirationDate = Math.round(Date.now() / 1000) + maxAge
    const token_value = parseCk['token']
    sessionFilter.urls.forEach(url => {
        const origin = new URL(url).origin
        const name = 'ulp_token'
        session.defaultSession.cookies.remove(origin, name)
        const setCookie: CookiesSetDetails = {
            url: origin,// cookie domain
            name,
            value: token_value,
            sameSite: 'no_restriction',//None , and secure must be true
            httpOnly: false,
            secure: true,
            expirationDate,
        }
        session.defaultSession.cookies.set(setCookie)
    })
}
// electron net.fetch 鉴权获取cookie
export const getUserInfoAndCookie = async (token: string) => {
    const response = await net.fetch(`https://xx/login?token=${token}`, { method: 'get' })
    const userInfo = await response.json()
    const cookies = response.headers.getSetCookie()
    const cookie = cookies?.find(ck => ck.includes('token'))
    return { userInfo,tokenCookie:cookie }
}

补充知识

Set-Cookie的值

developer.mozilla.org/zh-CN/docs/…

1. Expires=<date> 可选

以 HTTP 日期时间戳形式指定的 cookie 的最长有效时间 如果没有指定,那么会是一个会话期 cookie。会话在客户端被关闭时结束,这意味着会话期 cookie 会在彼时被移除。 如果设置了 Expires 日期,其截止时间与客户端相关,而非服务器的时间。

2. Max-Age=<number> 可选

在 cookie 过期之前需要经过的秒数。秒数为 0 或负值将会使 cookie 立刻过期。假如同时设置了 Expires 和 Max-Age 属性,那么 Max-Age 的优先级更高

Expires和Max-Age单位都是秒,前者绝对单位,后者相对单位,更灵活

Date.now,data.getTime
js获取时间戳,单位都为毫秒,Date.now,data.getTime
Date.now()适用于直接获取当前时间的场景,
而getTime()适用于获取特定日期对象的时间戳。
Date.now()相对更简单和直观,而getTime()需要先创建一个Date对象。

3. SameSite=<samesite-value> 可选

控制 cookie 是否随跨站请求一起发送,这样可以在一定程度上防范跨站请求伪造攻击(CSRF)

Strict 这意味浏览器仅对同一站点的请求发送 cookie,即请求来自设置 cookie 的站点。如果请求来自不同的域名或协议(即使是相同域名),则携带有 SameSite=Strict 属性的 cookie 不会被发送。

Lax 这意味着 cookie 不会在跨站请求中被发送,如:加载图像或框架(frame)的请求。但 cookie 在用户从外部站点导航到源站时,cookie 也会被发送(例如,访问一个链接)。这是 SameSite 属性未被设置时的默认行为。

None 这意味着浏览器在跨站和同站请求中均会发送 cookie。在设置这一属性值时,必须同时设置 Secure 属性,就像这样:SameSite=None; Secure。如果未设置 Secure,则会出现以下错误: Cookie "myCookie" rejected because it has the "SameSite=None" attribute but is missing the "secure" attribute.

4. Secure 可选

表示仅当请求通过 https: 协议(localhost 不受此限制)发送时才会将该 cookie 发送到服务器,因此其更能够抵抗中间人攻击。

SameSite=None
Secure 必须为true
SameSite 浏览器控制台无法修改

5. HttpOnly 可选

阻止 JavaScript 通过 Document.cookie 属性访问 cookie。注意,设置了 HttpOnly 的 cookie 仍然会通过 JavaScript 发起的请求发送。例如,调用 XMLHttpRequest.send() 或 fetch()

Domain=<domain-value> 可选 指定 cookie 可以送达的主机。 只能将值设置为当前域名或更高级别的域名(除非是公共后缀)。设置域名将会使 cookie 对指定的域名及其所有子域名可用。

若缺省,则此属性默认为当前文档 URL 的主机(不包括子域名)。

为了保证安全性,cookie无法设置除当前域名或者其父域名之外的其他domain

安全的跨域头

列入 CORS 白名单的标头还必须满足以下要求: Content-Type 的 MIME 类型的解析值(忽略参数)需要是 application/x-www-form-urlencoded、multipart/form-data 和 text/plain 中的一个。

如果不满足 服务器需要设置Access-Control-Allow-Headers Content-Type eg post 跨域请求 传递json参数 Content-Type:application/json,此刻需要单独设置

juejin.cn/post/731584…
stackoverflow.com/questions/1…