带cookie跨域问题的思路以及echo的解决方案

时间:2023-03-10 07:20:37
带cookie跨域问题的思路以及echo的解决方案

问题起因

前后端分离,前端要访问后端资源,而且需要携带cookie信息,这时碰到了跨域问题。一开始以为设置为允许跨域allow_origins为即可。可是浏览器还是拦截的请求,于是查看跨域规则,原来跨域allow_origins为时,只允许简单的跨域,比如get,post,但是如果携带cookie,则会出现失败。

思路

后来查看文档,原来按照跨域请求的规则,当跨域和来源一致时才可以携带cookie。详情见阮一峰的博客

echo框架中的解决办法

有了思路就好办了,echo框架中的跨域设置不够详细,做不到设置来源跨域。于是我修改了一下其中的跨域中间件,增加了一子域名的跨域。

实际上可以修改为,任意来源的跨域,但是这样就不能保证安全了,不过如果是做接口平台倒是可以这么办。

完整代码为:

package echo_cors

import (
"net/http"
"strconv"
"strings" "github.com/labstack/echo"
"github.com/labstack/echo/middleware"
) type (
// CORSConfig defines the config for CORS middleware.
CORSConfig struct {
// Skipper defines a function to skip middleware.
Skipper middleware.Skipper // AllowOrigin defines a list of origins that may access the resource.
// Optional. Default value []string{"*"}.
AllowOrigins []string `json:"allow_origins"` // AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods.
AllowMethods []string `json:"allow_methods"` // AllowHeaders defines a list of request headers that can be used when
// making the actual request. This in response to a preflight request.
// Optional. Default value []string{}.
AllowHeaders []string `json:"allow_headers"` // AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials.
// Optional. Default value false.
AllowCredentials bool `json:"allow_credentials"` // ExposeHeaders defines a whitelist headers that clients are allowed to
// access.
// Optional. Default value []string{}.
ExposeHeaders []string `json:"expose_headers"` // MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached.
// Optional. Default value 0.
MaxAge int `json:"max_age"`
AllowSubDomain bool
MainDomain string
AllowAllHost bool
}
) var (
// DefaultCORSConfig is the default CORS middleware config.
DefaultCORSConfig = CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}
) // CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
func CORS() echo.MiddlewareFunc {
return CORSWithConfig(DefaultCORSConfig)
} // CORSWithConfig returns a CORS middleware with config.
// See: `CORS()`.
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultCORSConfig.Skipper
}
if len(config.AllowOrigins) == 0 {
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
}
if len(config.AllowMethods) == 0 {
config.AllowMethods = DefaultCORSConfig.AllowMethods
} allowMethods := strings.Join(config.AllowMethods, ",")
allowHeaders := strings.Join(config.AllowHeaders, ",")
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
maxAge := strconv.Itoa(config.MaxAge) return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
} req := c.Request()
res := c.Response()
origin := req.Header.Get(echo.HeaderOrigin)
allowOrigin := "" // Check allowed origins
for _, o := range config.AllowOrigins {
if o == "*" || o == origin {
allowOrigin = o
break
}
}
if config.AllowSubDomain && config.MainDomain != "" {
if strings.Contains(origin, config.MainDomain) {
allowOrigin = origin
}
}
if config.AllowAllHost {
allowOrigin = c.Scheme()+"://"+req.Host
}
// Simple request
if req.Method != echo.OPTIONS {
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
if exposeHeaders != "" {
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
}
return next(c)
} // Preflight request
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
if allowHeaders != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
} else {
h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
if h != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
}
}
if config.MaxAge > 0 {
res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
}
return c.NoContent(http.StatusNoContent)
}
}
}

用法

已经放到github

这里增加了三个变量,AllowSubDomain,允许二级域名,MainDomain根域名,AllowAllHost 允许所有的跨域

  • CORSWithConfig(CORSConfig{AllowCredentials:true,AllowSubDomain:true,MainDomain:"main.com"}) 允许子域名跨域
  • CORSWithConfig(CORSConfig{AllowCredentials:true,AllowAllHost:true})

    对于js,也要做对应修改,axios的修改如下:
const Axios = axios.create({
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
withCredentials: true
}); Axios.interceptors.request.use(
config => {
if (config.method === 'post' || config.method === 'put') {
if (config.data) {
var queryString = Object.keys(config.data)
.map(key => {
return encodeURIComponent(key) + '=' + encodeURIComponent(config.data[key]);
})
.join('&');
config.data = queryString;
}
return config;
}
return config;
},
error => {
return Promise.reject(error);
}
);