web代码: 一句话: 要显式配置withCredentials=true还是false,因为不同浏览器默认值不同. 需要cookie就true
浏览器: 规则执行者,给前后端进行各种限制,其实纯粹退裤子放屁.
代理服务器(正向or 反向代理): 我能随便修改请求和响应
1 服务端全开的配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("*")
.maxAge(3600)
.allowCredentials(true);
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
预检请求后的正常请求里, 服务端返回:
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.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
此时,此时后台还是可以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
2 代理服务器的处置
本质上就是修改预检请求和正常请求的响应头,欺骗浏览器.
反向代理: nginx
开发时localhost的正向代理: nodejs,或者dart的shelf-proxy
写法应该参照上面贴出的抓包的响应头来处理,而不是全部设置为*
spring boot cros配置里全部设置为*时,spring 有进行针对性处理. 那么我们自己编写修改响应头代码时,也要遵守对应的规范:
对shelf-proxy的改写:
修改的完全版本.
代码库为: web_dev_proxy_shelf
// 修改响应头
//有时后台写了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'] = "*"
clientResponse.headers['Access-Control-Allow-Methods'] = "*"
clientResponse.headers['Access-Control-Expose-Headers'] = headerStr
clientResponse.headers['Access-Control-Max-Age'] = '36000'
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.
ngnix的配置
一般测试环境正式环境部署时,都是关掉spring boot里的cors配置,使用nginx重写响应头. 逻辑和上面的shelf-proxy的逻辑一致.
如果项目需要使用cookie(web项目一般都需要)
网上一搜,多数跨域配置都用不了,都写的什么玩意.
这一篇比较靠谱: 014.Nginx跨域配置
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';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain, charset=utf-8';
return 204;
......
注意:注意:注意:
上面是add_header,要生效就要把web服务器内部自己的cors配置关掉,否则出现多个响应头. 导致跨域失败.
如果被代理的服务器是别人的,有部分这些响应头,则会造成跨域失败. 此时,应该去直接修改响应头.
修改响应头可参考:
nginx替换响应头(重点:如何在替换时加上if判断)
如果项目不需要使用cookie
在上面基础上修改.
add_header 'Access-Control-Allow-Credentials' "true";
此时可以设置*了:
add_header 'Access-Control-Allow-Origin' *
二 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() + "
print("reset set-cookie header from $cookie to \n ${clientResponse.headers['set-cookie']}\n")
上线时,一般是应用服务器写cookie时配置好
比如普通返回时:
StpUtil.logout();
ResponseCookie cookie2 = ResponseCookie.from("navi-token","")
.httpOnly(false)
.secure(true)
.path("/")
.maxAge(10L)
.sameSite("None")
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie2.toString());
又比如sa-token里配置:
@Bean
@Primary
public SaTokenConfig getSaTokenConfigPrimary() {
SaTokenConfig config = new SaTokenConfig();
config.setTokenName("navi-token");
config.setActivityTimeout(-1);
config.setIsConcurrent(false);
config.setIsShare(false);
config.setTokenStyle("uuid");
config.setIsLog(true);
SaCookieConfig cookieConfig = new SaCookieConfig();
cookieConfig.setSameSite("None");
cookieConfig.setSecure(true);
cookieConfig.setDomain(".xxxxx.tech");
config.setCookie(cookieConfig);
return config;
粉丝