使用 Sa-Token 的全局过滤器解决跨域问题(三种方式全版)

warning: 这篇文章距离上次修改已过419天,其中的内容可能已经有所变动。

作者:省长
链接:https://juejin.cn/post/7247376558367981627
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


在 web 开发中,跨域绝对是比较折磨新同学的一个问题,本文将讲解三种常见的跨域情形,并讲解如何使用 Sa-Token 框架解决跨域问题。

什么情况下会发生跨域

简单理解,就是你在 A 域名下的页面,去调用 B 域名的接口,浏览器感觉你这次调用可能是不安全的请求行为,于是它需要用 cors 安全策略来确认一下这个请求是由用户真实的意愿发出的,而不是被 csrf 伪造请求攻击偷偷发送的。(这么说只是为了方便大家理解,不是特别严谨,实际上同域名下部分情形也会出现跨域问题)

请仔细理解上面这段话,因为它说明了两点:

  • 跨域不是后端接口对前端浏览器的限制,而是前端浏览器对用户的限制。
  • 跨域不是在保护后端接口免受攻击,而是浏览器在保护用户,避免用户发送自己不想发送的请求。

请一定要记住上面跨域的本质,明白了症状和原因,我们才能对症下药。

一般情况下,我们会碰到三种跨域场景:

  • 1、本地页面调用测试服务器,只在项目开发阶段会有跨域问题。(比较简单)
  • 2、使用 header 头提交 token,产生的跨域问题。(比较常见+通用,推荐使用)
  • 3、使用第三方 Cookie 提交 token,产生的跨域问题。(最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题)

跨域情形一:只在项目开发阶段会有跨域问题

有些公司项目的开发方式为:

  • 在项目开发时:使用本地页面调用测试服务器接口。(域名不同,存在跨域问题)
  • 在项目部署时:将后端接口和前端页面部署在同一域名下。(域名一致,不存在跨域问题)

这种情况下比较好解决,在代码层面我们无需任何更改,只在前端客户端做出一定的更改就行了。比如说:在前端配置一个代理服务器,或者修改一下 Chrome 客户端使其去除跨域限制。

具体的方案有很多,大家可参考这篇博客:手把手教你解决web前端跨域问题

上面是说的普通前后端分离开发,而在APP、小程序 开发中,其天然就是个没有跨域限制的客户端,我们什么都不用做就能解决跨域问题。

跨域情形二:使用 header 头提交 token,产生的跨域问题(比较常见+通用,推荐使用)

当你使用 header 头提交 token 时,会产生跨域问题。此方案比较常见+通用,推荐使用。

jquery 代码示例:

$.ajax({
        url: "/user/getInfo",
        type: "post", 
        data: {},
        dataType: 'json',
        headers: {
            "X-Requested-With": "XMLHttpRequest",
            // 重点处:请求的 header 头里塞入自定义参数
            "satoken": localStorage.getItem("satoken")
        },
        success: function(res){
            console.log(res);
        },
        error: function(xhr, type, errorThrown){
            return alert("异常:" + JSON.stringify(xhr));
        }
    });

Axios 代码示例:

axios({
        url: "/user/getInfo",
        method: 'post',
        data: {},
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            // 重点处:请求的 header 头里塞入自定义参数
            "satoken": localStorage.getItem("satoken")
        }
    }).
    then(function (response) { // 成功时执行
        const res = response.data;
        console.log(res);
    }).
    catch(function (error) {
        return alert("异常:" + JSON.stringify(error));
    });

此时在后端,我们应该添加以下响应头:

/**
 * [Sa-Token 权限认证] 配置类 
 *
 * @author click33
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    /**
     * 注册 [Sa-Token 全局过滤器] 
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                
                // 指定 [拦截路由] 与 [放行路由]
                .addInclude("/**").addExclude("/favicon.ico")
                
                // 认证函数: 每次请求执行 
                .setAuth(obj -> {
                    SaManager.getLog().debug("----- 请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
                    // ...
                })
                
                // 异常处理函数:每次认证函数发生异常时执行此函数 
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    SaHolder.getResponse()

                    // ---------- 设置跨域响应头 ----------
                    // 允许指定域访问跨域资源
                    .setHeader("Access-Control-Allow-Origin", "*")
                    // 允许所有请求方式
                    .setHeader("Access-Control-Allow-Methods", "*")
                    // 允许的header参数
                    .setHeader("Access-Control-Allow-Headers", "*")
                    // 有效时间
                    .setHeader("Access-Control-Max-Age", "3600")
                    ;
                    
                    // 如果是预检请求,则立即返回到前端 
                    SaRouter.match(SaHttpMethod.OPTIONS)
                        .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                        .back();
                });
    }
}

如果你的项目是 WebFlux 环境,只需要把过滤器名称从 SaServletFilter 更换为 SaReactorFilter 即可,其它保持不变。

跨域情形三:使用第三方 Cookie 提交 token,产生的跨域问题。

这是最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题。

jquery 代码示例:

$.ajax({
        url: "/user/getInfo",
        type: "post", 
        data: {},
        dataType: 'json',
        // 重点处:指定是跨域模式,需要提交第三方 Cookie 
        crossDomain: true,
        xhrFields:{
            withCredentials: true
        },
        headers: {
            "X-Requested-With": "XMLHttpRequest"
        },
        success: function(res){
            console.log(res);
        },
        error: function(xhr, type, errorThrown){
            return alert("异常:" + JSON.stringify(xhr));
        }
    });

Axios 代码示例:

axios({
        url: "/user/getInfo",
        method: 'post',
        data: {},
        // 重点处:开启第三方 Cookie 
        withCredentials: true,
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        }
    }).
    then(function (response) { // 成功时执行
        console.log(res);
    }).
    catch(function (error) {
        return alert("异常:" + JSON.stringify(error));
    });

此时在后端,我们应该添加以下响应头:

/**
 * [Sa-Token 权限认证] 配置类 
 *
 * @author click33
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    /**
     * 注册 [Sa-Token 全局过滤器] 
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                
                // 指定 [拦截路由] 与 [放行路由]
                .addInclude("/**").addExclude("/favicon.ico")
                
                // 认证函数: 每次请求执行 
                .setAuth(obj -> {
                    SaManager.getLog().debug("----- 请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
                    // ...
                })
                
                // 异常处理函数:每次认证函数发生异常时执行此函数 
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {

                    // 获得客户端domain
                    SaRequest request = SaHolder.getRequest();
                    String origin = request.getHeader("Origin");
                    if (origin == null) {
                        origin = request.getHeader("Referer");
                    }

                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                    // 允许第三方 Cookie 
                    .setHeader("Access-Control-Allow-Credentials", "true")
                    // 允许指定域访问跨域资源
                    .setHeader("Access-Control-Allow-Origin", origin)
                    // 允许所有请求方式
                    .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                    // 允许的header参数
                    .setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken")
                    // 有效时间
                    .setHeader("Access-Control-Max-Age", "3600");
                    
                    // 如果是预检请求,则立即返回到前端 
                    SaRouter.match(SaHttpMethod.OPTIONS)
                        .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                        .back();
                });
    }
}

如果你的项目是 WebFlux 环境,只需要把过滤器名称从 SaServletFilter 更换为 SaReactorFilter 即可,其它保持不变。

none
最后修改于:2024年08月29日 10:43

已有 31 条评论

  1. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  2. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  3. 新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  4. 新项目准备上线,寻找志同道合的合作伙伴

  5. 文章中的实用建议和操作指南,让读者受益匪浅,值得珍藏。

  6. ?诗歌散文评语?

  7. 悬念设置恰到好处,牢牢抓住读者注意力。

  8. 字里行间流露出真挚的情感,让人感同身受,共鸣不已。

  9. 选材新颖独特,通过细节描写赋予主题鲜活生命力。

  10. 哈哈哈,写的太好了https://www.lawjida.com/

  11. 哈哈哈,写的太好了https://www.lawjida.com/

  12. 你的文章让我感受到了正能量,非常棒! http://www.55baobei.com/bHVhiVQ7CU.html

  13. 《逆天邪神 动态漫画第一季》国产动漫高清在线免费观看:https://www.jgz518.com/xingkong/48530.html

  14. 你的文章让我感受到了正能量,非常棒! https://www.4006400989.com/qyvideo/9942.html

  15. 你的文章让我感受到了正能量,非常棒! http://www.55baobei.com/w6proy7ZlC.html

  16. 你的文章充满了创意,真是让人惊喜。 https://www.4006400989.com/qyvideo/80382.html

  17. 你的才华让人惊叹,你是我的榜样。 https://www.4006400989.com/qyvideo/24746.html

  18. 《谜证2017》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/35118.html

  19. 你的文章充满了创意,真是让人惊喜。 https://www.yonboz.com/video/92014.html

  20. 每次看到你的文章,我都觉得时间过得好快。 https://www.yonboz.com/video/79208.html

添加新评论