设置cookie

golang 跨子域设置cookie

参考文档: https://golang.0voice.com/?id=9784

在使用 Golang 设置跨子域的 Cookie 时,你需要确保以下步骤:

  1. 在服务器端设置允许跨域请求,包括设置适当的 CORS(Cross-Origin Resource Sharing)响应头。可以通过 Access-Control-Allow-Origin 来指定允许跨域请求的源。
  2. 在设置 Cookie 时,确保指定了 Domain 字段为主域名以及子域名的共享父级域名。这样可以使得该 Cookie 能够被多个子域名所共享。

下面是一个示例代码片段,展示了如何在 Golang 中进行跨子域设置 Cookie:

package main
import (
	"net/http"
)
func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
	// 设置允许跨域请求
	w.Header().Set("Access-Control-Allow-Origin", "http://example.com") // 替换成实际允许的源
	// 设置 Cookie
	cookie := http.Cookie{
		Name:     "my-cookie",
		Value:    "cookie-value",
		Path:     "/",
		Domain:   ".example.com", // 替换成实际的共享父级域名
		HttpOnly: true,
	}
	http.SetCookie(w, &cookie)
	w.Write([]byte("Cookie set successfully"))
}

请注意替换示例中的实际允许的源和共享父级域名。此外,还可以根据需要设置其他 Cookie 属性,例如 MaxAgeSecure 等。

Domain 设置有校验

只有有效的domain才会设置到cookie中:

net/http/cookie.go

// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func SetCookie(w ResponseWriter, cookie *Cookie) {
	if v := cookie.String(); v != "" {
		w.Header().Add("Set-Cookie", v)
	}
}

cookie.String()会校验Domain:

// String returns the serialization of the cookie for use in a Cookie
// header (if only Name and Value are set) or a Set-Cookie response
// header (if other fields are set).
// If c is nil or c.Name is invalid, the empty string is returned.
func (c *Cookie) String() string {
	if c == nil || !isCookieNameValid(c.Name) {
		return ""
	}
	// extraCookieLength derived from typical length of cookie attributes
	// see RFC 6265 Sec 4.1.
	const extraCookieLength = 110
	var b strings.Builder
	b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
	b.WriteString(c.Name)
	b.WriteRune('=')
	b.WriteString(sanitizeCookieValue(c.Value))

	if len(c.Path) > 0 {
		b.WriteString("; Path=")
		b.WriteString(sanitizeCookiePath(c.Path))
	}
	if len(c.Domain) > 0 {
		if validCookieDomain(c.Domain) {
			// A c.Domain containing illegal characters is not
			// sanitized but simply dropped which turns the cookie
			// into a host-only cookie. A leading dot is okay
			// but won't be sent.
			d := c.Domain
			if d[0] == '.' {
				d = d[1:]
			}
			b.WriteString("; Domain=")
			b.WriteString(d)
		} else {
			log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain)
		}
	}
}

validCookieDomain(c.Domain)的校验实现:

// validCookieDomain reports whether v is a valid cookie domain-value.
func validCookieDomain(v string) bool {
	if isCookieDomainName(v) {
		return true
	}
	if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
		return true
	}
	return false
}

// isCookieDomainName reports whether s is a valid domain name or a valid
// domain name with a leading dot '.'.  It is almost a direct copy of
// package net's isDomainName.
func isCookieDomainName(s string) bool {
	if len(s) == 0 {
		return false
	}
	if len(s) > 255 {
		return false
	}

	if s[0] == '.' {
		// A cookie a domain attribute may start with a leading dot.
		s = s[1:]
	}
	last := byte('.')
	ok := false // Ok once we've seen a letter.
	partlen := 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		default:
			return false
		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
			// No '_' allowed here (in contrast to package net).
			ok = true
			partlen++
		case '0' <= c && c <= '9':
			// fine
			partlen++
		case c == '-':
			// Byte before dash cannot be dot.
			if last == '.' {
				return false
			}
			partlen++
		case c == '.':
			// Byte before dot cannot be dot, dash.
			if last == '.' || last == '-' {
				return false
			}
			if partlen > 63 || partlen == 0 {
				return false
			}
			partlen = 0
		}
		last = c
	}
	if last == '-' || partlen > 63 {
		return false
	}

	return ok
}

说明:

  • .wl_dev.com 不是一个有效的Domain。

测试代码:

package main

import (
	"fmt"
	"net"
)

func isCookieDomainName(s string) bool {
	if len(s) == 0 {
		return false
	}
	if len(s) > 255 {
		return false
	}

	if s[0] == '.' {
		// A cookie a domain attribute may start with a leading dot.
		s = s[1:]
	}
	last := byte('.')
	ok := false // Ok once we've seen a letter.
	partlen := 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		default:
			return false
		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
			// No '_' allowed here (in contrast to package net).
			ok = true
			partlen++
		case '0' <= c && c <= '9':
			// fine
			partlen++
		case c == '-':
			// Byte before dash cannot be dot.
			if last == '.' {
				return false
			}
			partlen++
		case c == '.':
			// Byte before dot cannot be dot, dash.
			if last == '.' || last == '-' {
				return false
			}
			if partlen > 63 || partlen == 0 {
				return false
			}
			partlen = 0
		}
		last = c
	}
	if last == '-' || partlen > 63 {
		return false
	}

	return ok
}

func main() {
	fmt.Println(isCookieDomainName(".wldev.com"))
	fmt.Println(isCookieDomainName(".wl119.com"))
	fmt.Println(isCookieDomainName(".wl_dev.com"))
	fmt.Println(net.ParseIP("wl_dev.com"))
	fmt.Println(net.ParseIP("wl119.club"))
}

输出:

true
true
false
<nil>
<nil>

Domain Matching

参考文档: https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3

Domain的匹配规则:

A string domain-matches a given domain string if at least one of the following conditions hold:

  1. The domain string and the string are identical. (Note that both the domain string and the string will have been canonicalized to lower case at this point.)

  2. All of the following conditions hold:

  • The domain string is a suffix of the string.
  • The last character of the string that is not included in the domain string is a %x2E (".") character.
  • The string is a host name (i.e., not an IP address).

http请求什么时候会带上cookie信息

参考文档: https://blog.csdn.net/john1337/article/details/104571244

Set-Cookie响应头字段(Response header):是服务器发送到浏览器或者其他客户端的一些信息,一般用于登陆成功的情况下返回给客户端的凭证信息,然后下次请求时会带上这个cookie,这样服务器端就能知道是来自哪个用户的请求了。

Cookie请求头字段是客户端发送请求到服务器端时发送的信息(满足一定条件下浏览器自动完成,无需前端代码辅助)。

下表为Set-Cookie响应头可以设置的属性

NAME=VALUE 赋予 Cookie 的名称和其值(必需项)
expires=DATE Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止)
path=PATH 将服务器上的文件目录作为Cookie的适用对象(若不指定则默 认为文档所在的文件目录)
domain=域名 作为 Cookie 适用对象的域名 (若不指定则默认为创建 Cookie 的服务器的域名)
Secure 仅在 HTTPS 安全通信时才会发送 Cookie
HttpOnly 加以限制, 使 Cookie 不能被 JavaScript 脚本访问

请看上面标红的三个属性(path, domain, Secure), 拿一个Http POST请求来说 http://aaa.www.com/xxxxx/list

如果满足下面几个条件:

  1. 浏览器端某个Cookie的domain字段等于aaa.www.com或者www.com
  2. 都是http或者https,或者不同的情况下Secure属性为false
  3. 要发送请求的路径,即上面的xxxxx跟浏览器端Cookie的path属性必须一致,或者是浏览器端Cookie的path的子目录,比如浏览器端Cookie的path为/test,那么xxxx必须为/test或者/test/xxxx等子目录才可以

注: 上面3个条件必须同时满足,否则该Post请求就不能自动带上浏览器端已存在的Cookie