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

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

作者:省长
链接: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

已有 27 条评论

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

  2. ?诗歌散文评语?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  17. yzwsclcj.com

  18. 你的才华让人惊叹,请继续保持。 http://www.55baobei.com/710IqAzn9W.html

  19. 你的文章让我感受到了无尽的欢乐,谢谢分享。 http://www.55baobei.com/KJftpYzuCd.html

  20. 你的文章充满了智慧,让人敬佩。 http://www.55baobei.com/hdPvIW21mY.html

添加新评论