Date: 2021.03.14
Author: jwensh
前后端分离开发,js调用api跨域问题
开发平台的时候,出于开发模式的原因,使用了前后端分离方式,前端使用VUE
后端使用Flask
, 在服务部署上,两个端是分开部署的,这个导致了一个问题:访问跨域
1. 遇到的问题
前端页面中使用axios调用api接口,出现报错,从console里可查看到:
Access to XMLHttpRequest at 'http://localhost:8081/api/v3.0/ci/history?user=jwensh' from origin
'http://localhost:9527' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials'
header in the response is '' which must be 'true' when the
提示:Access-Control-Allow-Credentials
的值, 响应中的头是必须是’true’ , 经过一番调整后,又有报错
Access to XMLHttpRequest at 'http://localhost:8081/api/v3.0/ci/history?user=jwensh' from origin
'http://localhost:9527' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains
the invalid value 'true'.
提示:Access-Control-Allow-Origin
头文件包含无效值“true”
,应该是我写错了值,调整后又报错
Access to XMLHttpRequest at 'http://localhost:8081/api/v3.0/ci/history?user=jwensh' from origin
'http://localhost:9527' has been blocked by CORS policy: 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.
提示: Access-Control-Allow-Origin
的值。 当请求的凭据模式是'include'
时,响应中的头不能是通配符'*'
。XMLHttpRequest
发起的请求的凭据模式由withCredentials
属性控制
2. 根据提示来解决
首选看下header都有哪些,且使用的范围(来自flask_cors
源码)或查看 mozilla的跨源资源共享
#Response Headers
ACL_ORIGIN = ‘Access-Control-Allow-Origin’
ACL_METHODS = ‘Access-Control-Allow-Methods’
ACL_ALLOW_HEADERS = ‘Access-Control-Allow-Headers’
ACL_EXPOSE_HEADERS = ‘Access-Control-Expose-Headers’
ACL_CREDENTIALS = ‘Access-Control-Allow-Credentials’
ACL_MAX_AGE = ‘Access-Control-Max-Age’
#Request Header
ACL_REQUEST_METHOD = ‘Access-Control-Request-Method’
ACL_REQUEST_HEADERS = ‘Access-Control-Request-Headers’
使用的地方时一个是服务器,一个是浏览器
先看下两个关键词什么意思:
Access-Control-Allow-Credentials
他是服务端下发到客户端的 response 中头部字段,意义是允许客户端携带验证信息,例如 cookie 之类的。
这样客户端在发起跨域请求的时候,就可以携带允许的头,还可以携带验证信息的头。
由于前端是请求框架是axios
,并设置了 withCredentials: true
,意思是客户端想要携带验证信息头
这里需要看下服务端设置的 ‘Credentials’ 的值是什么 ,表示不允许携带信息头; 看下flask_cors
的默认设置(其他web开发框架,也都有对于的标准设定), 其中 CORS
函数用来做全局的配置, @cross_origin
来实现特定路由的配置:
DEFAULT_OPTIONS = dict(origins='*',
methods=ALL_METHODS,
allow_headers='*',
expose_headers=None,
supports_credentials=False,
max_age=None,
send_wildcard=False,
automatic_options=True,
vary_header=True,
resources=r'/*',
intercept_exceptions=True,
always_send=True)
看到默认值: supports_credentials=False
, 那就是第一条报错的原因了
参数说明:
参数 | 类型 | Head | 默认 | 说明 |
resources | 字典、迭代器或字符串 | 无 | 全部 | 配置允许跨域的路由接口 |
origins | 列表、字符串或正则表达式 | Access-Control-Allow-Origin | * | 配置允许跨域访问的源 |
methods | 列表、字符串 | Access-Control-Allow-Methods | [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE] | 配置跨域支持的请求方式 |
expose_headers | 列表、字符串 | Access-Control-Expose-Headers | None | 自定义请求响应的Head信息 |
allow_headers | 列表、字符串或正则表达式 | Access-Control-Request-Headers | * | 配置允许跨域的请求头 |
| 布尔值 | Access-Control-Allow-Credentials | False | 是否允许请求发送cookie |
max_age | timedelta、整数、字符串 | Access-Control-Max-Age | None | 预检请求的有效时长 |
如果前端和后端交互的时候,并没有使用到 cookie,那前端没有必要设置 withCredentials: true
Access-Control-Allow-Origin
从字面意思就能看出,是允许哪些网站能够访问,即配置允许跨域访问的源
那服务器端如何处理?
@app.after_request
def after_request(resp):
resp.headers['Access-Control-Allow-Credentials'] = 'true'
origin = 'http://my-frontend/'
try:
if request.environ['HTTP_ORIGIN']:
origin = request.environ['HTTP_ORIGIN']
except:
pass
resp.headers['Access-Control-Allow-Origin'] = origin
resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'
resp.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'
return resp
通过这两个参数我解决了我的跨域问题
3. 总结
- 出于安全性,浏览器限制脚本内发起的跨源HTTP请求: 找到问题是因为什么发生的,且作用于谁?
- 根据提示或通过调试,来逐一解决(本文没有详细的解决步骤,主要未来记录问题)
- 跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
参考资料