同源策略与安全机制
在Web安全中,同源策略(Same-Origin Policy)是浏览器实施的一项重要安全机制,用于保护用户信息安全,防止恶意网站访问其他网站的数据。 什么是同源? 两个URL在以下三个方面完全一致时,才被视为同源:
- 协议(Protocol):如http://或https://
- 域名(Domain):如www.example.com
- 端口号(Port):如80或443(默认端口可省略)
示例分析:
URL 1 | URL 2 | 是否同源 | 原因 |
---|---|---|---|
https://www.example.com | https://www.example.com | 是 | 协议、域名、端口完全相同 |
http://www.example.com | https://www.example.com | 否 | 协议不同(http vs https) |
https://www.example.com | https://api.example.com | 否 | 子域名不同(www vs api) |
https://www.example.com:80 | https://www.example.com:8080 | 否 | 端口号不同(80 vs 8080) |
同源策略的作用:
- 防止恶意网站窃取用户敏感信息
- 限制跨站脚本攻击(XSS)
- 阻止未经授权的数据访问
跨域是什么?
跨域,全称叫做跨域资源共享。是浏览器的一种保护机制,浏览器最基本的安全功能。如果缺少了同源策略,浏览器的正常功能都会受到影响。同源策略下只允许网页请求同一域名下的服务 ,即协议,域名和端口都要保持一致。更详细的同源政策可以参考「MDN文档 同源策略」
在同源策略下,会有以下限制:
- 无法获取非同源的 Cookie、LocalStorage、SessionStorage 等
- 无法获取非同源的 dom
- 无法向非同源的服务器发送 ajax 请求
在日常开发中,前后端分离,经常需要在不同源的情况下,需要 ajax 请求数据,携带cookie等,那我们就要规避这种限制。
如何解决跨域问题?
前端本地调试,解决跨域问题
跨域是浏览器的保护机制,所以我们解决跨域问题就可以*在前端开发时配置代理,中转请求。我们使用一个中转服务器来发送请求和接收响应。大部分的前端脚手架都支持代理。 例如在一个用vite脚手架创建的项目中,在vue.config.js中去配置。
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});
如果是next.js项目,在next.config.js中配置。
module.exports = {
async rewrites() {
return [
{ source: '/api/:path*', destination: 'http://localhost:3000/:path*' },
];
},
};
后端配置,解决跨域问题
使用nginx、node中间件转发,将跨域转成同源
可以使用nginx转发,将跨域转成同源。跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。也是一种解决跨域问题的方法。
使用CORS,解决跨域问题
使用CORS,是后端服务器设置HTTP头,允许跨域请求。也是日常开发中最常用的解决跨域问题的方法。CORS 跨域的原理实际上是浏览器与服务器通过一些 HTTP 协议头来做一些约定和限制。可以查看 HTTP-访问控制CORS
下面是使用CORS解决跨域问题的Golang示例, 其它语言代码可以通过chatgpt生成类似处理:
//cmd/serve.go
func newServer(cfg *config.Config) *gin.Engine {
//... update cfg
gin.DisableConsoleColor()
r := gin.New()
l := utils.DefaultLogger()
r.Use(utils.MiddlewareCORS)
r.Use(utils.GinLogger(l), utils.GinRecovery(l, true))
r.GET("/api/v1/health", func(c *gin.Context) {
c.String(http.StatusOK, "ok")
})
// ...
return r
}
// utils/middleware.go
func MiddlewareCORS(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Cache-Control, Cookie, Content-Encoding")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}