百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Ajax跨域请求的两种实现方式 ajax跨域如何实现

bigegpt 2024-10-12 05:55 12 浏览

一、跨域请求痛点

最近网站新增了一个域名B用于分离不同的功能。但是需要复用服务器的高防等服务,但是服务和原有域名A绑定,所以新域名B需要直接去调用域名A。

一开始想使用CNAME的方式,让B直接指向A。但是Https支持性有点问题,需要多域名证书。也考虑过反向代理,但是代理服务器的性能和高防等又是一个问题。

最终决定在域名B的网页中,所有请求都直接去调用域名A的接口。于是就遇到了跨域请求的问题。

二、跨域请求的实现方式

网上找了许多资料来实现跨域请求。最终预估下来,有两种方案比较靠谱:通过iframe实现和CORS方案

三、通过iframe实现

初步设想是加载一个域名A的iframe页面,然后通过postMessage将所有Ajax请求,转发给这个页面,通过这个页面来进行请求,最终将结果通过postMessage回发给外层的域名B页面。

于是开始实现:

前端使用的是React,所以实现了一个FrameHttp.js专门用法封装ajax调用。调用FrameHttp.ajax将所有外部Jquery请求转发给iframe中域名A的/util/ajaxrequest页面。

import Tools from "../Tools"

const FrameHttpCmd = {
    INIT: 1,
    REQUEST: 2,
    REQUEST_CALLBACK: 3,
}

class FrameHttp {
    static isInit = false
    static _request_buffer = []
    static frame = null
    static message_key = 0
    static message_key_max = 10000000
    static request_map = {}

    static init(domain) {
        var the_frame = document.createElement('iframe')
        let url_obj = new URL(domain)
        url_obj.pathname = Tools.getUrl('/util/ajaxrequest')
        the_frame.src = url_obj.toString()
        the_frame.style.visibility = 'hidden'
        the_frame.style.position = 'absolute'
        the_frame.style.width = 0
        the_frame.style.height = 0

        FrameHttp.frame = the_frame
        document.body.appendChild(the_frame)
        window.addEventListener('message', this.onMessage)
    }

    static _initFrame() {
        FrameHttp.isInit = true
        console.log('(INFO)FrameHttp._initFrame')
        for (let i = 0; i < FrameHttp._request_buffer.length; i++) {
            let arg = FrameHttp._request_buffer[i]
            FrameHttp.ajax(arg)
        }
        FrameHttp._request_buffer = []
    }

    static getMessageKey() {
        let message_key = FrameHttp.message_key
        FrameHttp.message_key = (FrameHttp.message_key+1)%FrameHttp.message_key_max 
        return message_key
    }

    static ajax(arg) {
        if (FrameHttp.isInit) {
            // console.log(arg)
            const { success, error, ...others } = arg
            let message_key = FrameHttp.getMessageKey()
            FrameHttp.request_map[message_key] = { success, error }
            FrameHttp.frame.contentWindow.postMessage({
                cmd: FrameHttpCmd.REQUEST,
                data: others,
                key: message_key,
            }, '*')
            console.log('(INFO)FrameHttp.ajax', others)
        }
        else {
            FrameHttp._request_buffer.push(arg)
            console.log('(INFO)FrameHttp.ajax:push buffer')
        }
    }

    static onMessage(e) {
        const { data } = e
        if (data.cmd == FrameHttpCmd.INIT) {
            FrameHttp._initFrame()
        }
        else if (data.cmd == FrameHttpCmd.REQUEST_CALLBACK) {
            // console.log(data.key, data.success)
            let item = FrameHttp.request_map[data.key]
            if (data.error) {
                if (item.error) {
                    item.error(data.error)
                }
            }
            else {
                if (item.success) {
                    item.success(data.success)
                }
            }
            delete FrameHttp.request_map[data.key]
        }
    }
}

export default FrameHttp
export { FrameHttpCmd }

然后域名A中的/util/ajaxrequest页面处理请求:

import React, { Component } from 'react'
import { FrameHttpCmd } from '../../../common_js/web_frame/FrameHttp'
import jquery from '../../../common_js/jquery.min'

class AjaxRequest extends Component {
    constructor(props) {
        super(props)

        this.onMessage = this.onMessage.bind(this)
    }

    onMessage(e) {
        const { cmd, data, key, cookie } = e.data
        if (cmd == FrameHttpCmd.REQUEST) {
            // console.log(key, data)
            console.log(document.cookie)
            jquery.ajax({
                ...data,
                success: (data)=>{
                    window.parent.postMessage({
                        cmd: FrameHttpCmd.REQUEST_CALLBACK,
                        key,
                        success: data,
                    }, '*')
                },
                error: ()=>{
                    window.parent.postMessage({
                        cmd: FrameHttpCmd.REQUEST_CALLBACK,
                        key,
                        error: 'error',
                    }, '*')
                },
            })
        }
    }

    componentDidMount() {
        window.parent.postMessage({
            cmd: FrameHttpCmd.INIT,
        }, '*')
        window.addEventListener('message', this.onMessage)
    }

    render() {
        return null
    }
}
                
export default AjaxRequest

如此实现后,发现iframe因为跨域问题无法加载

因为后端是由Django实现的,经过查阅发现,在views中需要进行处理,添加xframe_options_exempt装饰器,实现跨域加载iframe。

@xframe_options_exempt
def indexCross(request, *args, **kwargs):
    return render(request, 'index.html', {})

功能能够正常请求,但是请求中cookie并没有正常传送。

经过排查发现,对于跨域的iframe,google浏览器默认对于cookie中SameSite这个参数是LAX,会导致只有同源才能设置服务器返回的Set-Cookie。所以需要将服务器返回的Set-Cookie中指定SameSite为None,这样前端才能成功设置Cookie。

于是服务端Django引入了中间件django-cookies-samesite。settings.py进行如下修改:

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

# 中间件request按照注册顺序顺序执行,response按照注册顺序倒序执行
MIDDLEWARE = [
    'django_cookies_samesite.middleware.CookiesSameSite', # 此处response需要最后执行
    ...
]

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'

至此实现了iframe方式跨域。

四、CORS方案

这个需要前端Jquery加入两个参数:

jquery.ajax({
    ...
    xhrFields: {
        withCredentials: true
    },
    crossDomain: true,
})

然后服务端需要开启跨域请求支持。Django下引入中间件django-cors-headers。settings.py下如下设置:

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware', # 这个位置越高越好,至少要高于CommonMiddleware
    ...
]

# 跨域配置
CORS_ALLOW_CREDENTIALS = True
# CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = [
    'https://www.xxx.com',# 域名B
]
CORS_ALLOW_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
]
CORS_ALLOW_HEADERS = [
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
    'cache-control',
]

此方案也需要和iframe一样,解决cookie中SameSite的问题。

至此CORS方案跨域也实现了。

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...