技术经验谈 技术经验谈
首页
  • 最佳实践

    • 抓包
    • 数据库操作
  • ui

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 总纲
  • 整体开发框架
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

hss01248

一号线程序员
首页
  • 最佳实践

    • 抓包
    • 数据库操作
  • ui

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 总纲
  • 整体开发框架
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 学习资料

  • 技术选型

  • 要点

    • dart语言核心要点
    • 工程化

      • flutter依赖管理
      • flutter代码模板优化和禁用.android文件夹刷新
      • flutter客户端项目适配web做的一些工作
      • web跨域问题终结者
        • 1.1 效果描述
          • 1.1.1 服务端allowCredentials=true时
          • 1.1.1.2 此时预检请求时,服务端返回:
          • 1.1.2 如果服务端配置 .allowCredentials(false),那么响应头:
        • 对shelf-proxy的改写:
        • ngnix的配置
          • 如果项目需要使用cookie(web项目一般都需要)
          • 如果项目不需要使用cookie
        • 开发时,一般是本地代理服务器直接改:
        • 上线时,一般是应用服务器写cookie时配置好
      • flutter编译流程修改
      • flutter工程的模块化架构
      • flutter web编译瘦身
      • webview_flutter官方插件的增强-对inputfile和权限请求的支持
    • flutter异步
    • flutter工程化
    • flutter存储相关
    • flutter状态管理
    • flutter网络框架以及相关要点
    • flutter图片相关
    • flutter可观测性和调试
    • flutter插件开发
    • flutter路由管理
    • 遇到的问题
  • 经验

  • flutter
  • 要点
  • 工程化
hss01248
2022-10-11
目录

web跨域问题终结者

# 一 web请求跨域问题

# 背景知识

自行阅读

跨域资源共享 CORS 详解 (opens new window)

浏览器同源政策及其规避方法 (opens new window)

# 角色

  • web代码: 一句话: 要显式配置withCredentials=true还是false,因为不同浏览器默认值不同. 需要cookie就true
  • 浏览器: 规则执行者,给前后端进行各种限制,其实纯粹退裤子放屁.
  • 代理服务器(正向or 反向代理): 我能随便修改请求和响应
  • 服务端:

# 1 服务端全开的配置

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                //.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .allowedMethods("*")
                .allowedHeaders("*")
                .exposedHeaders("*")
                .maxAge(3600)
                //.exposedHeaders(RedisConstant.TOKEN_HEADER)
               .allowCredentials(true);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.1 效果描述

# 1.1.1 服务端allowCredentials=true时

划重点:

服务端必须返回特定的Access-Control-Allow-Headers和Access-Control-Allow-Origin,

不能是,否则会被浏览器拦截,认为跨域失败*

# 1.1.1.2 此时预检请求时,服务端返回:
  Access-Control-Allow-Credentials: true
  Access-Control-Allow-Headers: app-version, content-type, device-id, device-type, version-code
  Access-Control-Allow-Methods: POST
  Access-Control-Allow-Origin: http://localhost:60664
  Access-Control-Max-Age: 3600
  Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
  Connection: keep-alive
  Content-Length: 0
  Date: Tue, 11 Oct 2022 10:47:45 GMT
  Keep-Alive: timeout=60
  Vary: Origin
  Vary: Access-Control-Request-Method
  Vary: Access-Control-Request-Headers
1
2
3
4
5
6
7
8
9
10
11
12
13

预检请求后的正常请求里, 服务端返回:


  Access-Control-Allow-Credentials: true
  Access-Control-Allow-Origin: http://localhost:60664
  Connection: keep-alive
  Content-Type: application/json
  Date: Tue, 11 Oct 2022 10:47:47 GMT
  Keep-Alive: timeout=60
  Transfer-Encoding: chunked
  Vary: Origin
  Vary: Access-Control-Request-Method
  Vary: Access-Control-Request-Headers
1
2
3
4
5
6
7
8
9
10
11

# 1.1.2 如果服务端配置 .allowCredentials(false),那么响应头:

        Access-Control-Allow-Headers: app-version, content-type, device-id, device-type, version-code
        Access-Control-Allow-Methods: POST
        Access-Control-Allow-Origin: *
        Access-Control-Max-Age: 3600
        Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
        Connection: keep-alive
        Content-Length: 0
        Date: Tue, 11 Oct 2022 10:50:48 GMT
        Keep-Alive: timeout=60
        Vary: Origin
        Vary: Access-Control-Request-Method
        Vary: Access-Control-Request-Headers
1
2
3
4
5
6
7
8
9
10
11
12
  • 此时,此时后台还是可以set-cookie,但web端无法在下次请求携带

  • 此时,如果web端代码设置了Credentials=true,那么这个响应会被浏览器拦截掉,cors失败

  • 此时,如果web端设置withCredentials为false,则可以请求,但不携带cookie. 下次正常请求时,后端的响应头为:

  •  Access-Control-Allow-Origin: *
     Connection: keep-alive
     Content-Type: application/json
     Date: Tue, 11 Oct 2022 10:55:15 GMT
     Keep-Alive: timeout=60
     Transfer-Encoding: chunked
     Vary: Origin
     Vary: Access-Control-Request-Method
     Vary: Access-Control-Request-Headers
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 2 代理服务器的处置

本质上就是修改预检请求和正常请求的响应头,欺骗浏览器.

  • 反向代理: nginx
  • 开发时localhost的正向代理: nodejs,或者dart的shelf-proxy

写法应该参照上面贴出的抓包的响应头来处理,而不是全部设置为*

spring boot cros配置里全部设置为*时,spring 有进行针对性处理. 那么我们自己编写修改响应头代码时,也要遵守对应的规范:

# 对shelf-proxy的改写:

修改的完全版本.

代码库为: web_dev_proxy_shelf (opens new window)

// 修改响应头
  //有时后台写了Access-Control-Allow-Origin,那么server.defaultResponseHeaders的设置就会无效
  //if("OPTIONS" == (clientResponse?.request?.method??"")){
  Map<String, String> headers = clientResponse.headers;
  //不能同时多个
  headers.remove('access-control-allow-origin');
  headers.remove('access-control-allow-methods');
  headers.remove('access-control-allow-headers');
  headers.remove('access-control-expose-headers');
  headers.remove('access-control-max-age');
  headers.remove('access-control-allow-credentials');



  //你请求什么,就允许什么
  //access-control-request-headers 这个是chrome加了,所以在request!.headers里取不到
  //Request header field app-version is not allowed by Access-Control-Allow-Headers in preflight response.
  //Map<String, String> reqeustHeaders = clientResponse!.request!.headers!;
  String headerStr = clientResponse!.request!
      .headers['access-control-request-headers'].toString();
  //预检请求不会携带额外的header,所以下面拼接header没有鸟用, 要用access-control-request-headers取
  //reqeustHeaders.forEach((key, value) { headerStr = headerStr+","+key; });
  //access-control-request-headers

  if (headerStr == "null") {
    headerStr = "*";
  }
  clientResponse.headers['Access-Control-Allow-Headers'] = headerStr;
  //clientResponse.headers['Access-Control-Allow-Headers'] = "*";//预检请求里,['Access-Control-Allow-Credentials'] = 'true'时 不能用*
  clientResponse.headers['Access-Control-Allow-Methods'] = "*";  //GET,POST,PUT,OPTIONS
  clientResponse.headers['Access-Control-Expose-Headers'] = headerStr;
  clientResponse.headers['Access-Control-Max-Age'] = '36000'; //如果chrome开启了禁用缓存,那么每次都会发预检请求
  clientResponse.headers['Access-Control-Allow-Credentials'] = 'true';
  //预检请求: 设置了-Allow-Credentials'] = 'true'时,两个限制:
  // Access-Control-Allow-Origin'] 不能为 "*"  -Allow-Headers'] = "*"

  //clientResponse.headers['Access-Control-Allow-Origin'] = "*";
  String original = clientResponse!.request!.headers["Referer"].toString();
  if(original == "null"){
    original = "*";
  }
  if (original.endsWith("/")) {
    original = original.substring(0, original.length - 1);
  }
  clientResponse.headers['Access-Control-Allow-Origin'] = original;
  //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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# ngnix的配置

一般测试环境正式环境部署时,都是关掉spring boot里的cors配置,使用nginx重写响应头. 逻辑和上面的shelf-proxy的逻辑一致.

# 如果项目需要使用cookie(web项目一般都需要)

网上一搜,多数跨域配置都用不了,都写的什么玩意.

这一篇比较靠谱: 014.Nginx跨域配置 (opens new window)

location /pub/(.+) {
    if ($http_origin ~ <允许的域(正则匹配)>) {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        if ($request_method = "OPTIONS") {
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Access-Control-Allow-Methods' 'GET, POST,PUT, OPTIONS, DELETE';
            add_header 'Access-Control-Allow-Headers' 'reqid, nid, host, x-real-ip, x-forwarded-ip, event-type, event-id, accept, content-type';
            # 线上推荐写死特定的header,测试环境可以使用 $http_access_control_request_headers, 表示你请求什么我允许什么
            add_header 'Content-Length' 0;
            add_header 'Content-Type' 'text/plain, charset=utf-8';
            return 204;
        }
    }
    # 正常nginx配置
    ......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意:注意:注意:

上面是add_header,要生效就要把web服务器内部自己的cors配置关掉,否则出现多个响应头. 导致跨域失败.

如果被代理的服务器是别人的,有部分这些响应头,则会造成跨域失败. 此时,应该去直接修改响应头.

修改响应头可参考:

nginx替换响应头(重点:如何在替换时加上if判断) (opens new window)

# 如果项目不需要使用cookie

在上面基础上修改.

不需要:

  add_header 'Access-Control-Allow-Credentials' "true";
1

此时可以设置*了:

 add_header 'Access-Control-Allow-Origin' *
1

# 二 cookie的跨域

修改响应头里的set-cookie

将cookie的samesite设置为None,此时Secure也要设置为true.

域名一般设置为二级域名,比较通用

# 开发时,一般是本地代理服务器直接改:

void transferCookies(http.StreamedResponse clientResponse,String localHost) {
  String? cookie = clientResponse.headers['set-cookie'];
  if (cookie == null || cookie.isEmpty) {
    return;
  }
//服务器要发送多个 cookie,则应该在同一响应中发送多个 Set-Cookie 标头。
  Cookie cookie2 = Cookie.fromSetCookieValue(cookie);
  cookie2.secure = true;
  cookie2.httpOnly = false;
  cookie2.domain = localHost;
  clientResponse.headers['set-cookie'] = cookie2.toString() + ";SameSite=None;";

  print("reset set-cookie header from $cookie to \n ${clientResponse.headers['set-cookie']}\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 上线时,一般是应用服务器写cookie时配置好

比如普通返回时:

  StpUtil.logout();
        //将cookie里原token置为空:
        ResponseCookie cookie2 = ResponseCookie.from("navi-token","") // key & value
                .httpOnly(false)      // 禁止js读取
                .secure(true)     // 在http下也传输
//                    .domain("localhost")// 域名
                .path("/")       // path
                .maxAge(10L)   // 有效期10s
                .sameSite("None")  // 大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
                .build();
        response.setHeader(HttpHeaders.SET_COOKIE, cookie2.toString());
1
2
3
4
5
6
7
8
9
10
11

又比如sa-token里配置:

// 获取配置Bean (以代码的方式配置Sa-Token, 此配置会覆盖yml中的配置)
@Bean
@Primary
public SaTokenConfig getSaTokenConfigPrimary() {
    SaTokenConfig config = new SaTokenConfig();
    config.setTokenName("navi-token");             // token名称 (同时也是cookie名称)
    //config.setTimeout(30 * 24 * 60 * 60);       // token有效期,单位s 默认30天
    config.setActivityTimeout(-1);              // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
    config.setIsConcurrent(false);               // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
    config.setIsShare(false);                    // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
    config.setTokenStyle("uuid");               // token风格
    config.setIsLog(true);// 是否输出操作日志

    SaCookieConfig cookieConfig = new SaCookieConfig();
    cookieConfig.setSameSite("None");
    cookieConfig.setSecure(true);
    //cookieConfig.setHttpOnly(true);
    cookieConfig.setDomain(".xxxxx.tech");

    config.setCookie(cookieConfig);
    return config;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
编辑 (opens new window)
上次更新: 2022/12/15, 14:32:28
flutter客户端项目适配web做的一些工作
flutter编译流程修改

← flutter客户端项目适配web做的一些工作 flutter编译流程修改→

最近更新
01
截图后的自动压缩工具
12-27
02
图片视频文件根据exif批量重命名
12-27
03
chatgpt图片识别描述功能
02-20
更多文章>
Theme by Vdoing | Copyright © 2020-2025 | 粤ICP备20041795号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式