mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
Merge pull request #1549 from FlyingBlazer/cookie-secure-config
ghttp: add cookie security configurations
This commit is contained in:
@ -150,6 +150,18 @@ type ServerConfig struct {
|
||||
// It also affects the default storage for session id.
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
|
||||
// CookieSameSite specifies cookie SameSite property.
|
||||
// It also affects the default storage for session id.
|
||||
CookieSameSite string `json:"cookieSameSite"`
|
||||
|
||||
// CookieSameSite specifies cookie Secure property.
|
||||
// It also affects the default storage for session id.
|
||||
CookieSecure bool `json:"cookieSecure"`
|
||||
|
||||
// CookieSameSite specifies cookie HttpOnly property.
|
||||
// It also affects the default storage for session id.
|
||||
CookieHttpOnly bool `json:"cookieHttpOnly"`
|
||||
|
||||
// ======================================================================================================
|
||||
// Session.
|
||||
// ======================================================================================================
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -39,3 +40,25 @@ func (s *Server) GetCookiePath() string {
|
||||
func (s *Server) GetCookieDomain() string {
|
||||
return s.config.CookieDomain
|
||||
}
|
||||
|
||||
// GetCookieSameSite return CookieSameSite of server.
|
||||
func (s *Server) GetCookieSameSite() http.SameSite {
|
||||
switch s.config.CookieSameSite {
|
||||
case "lax":
|
||||
return http.SameSiteLaxMode
|
||||
case "none":
|
||||
return http.SameSiteNoneMode
|
||||
case "strict":
|
||||
return http.SameSiteStrictMode
|
||||
default:
|
||||
return http.SameSiteDefaultMode
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetCookieSecure() bool {
|
||||
return s.config.CookieSecure
|
||||
}
|
||||
|
||||
func (s *Server) GetCookieHttpOnly() bool {
|
||||
return s.config.CookieHttpOnly
|
||||
}
|
||||
|
||||
@ -21,6 +21,13 @@ type Cookie struct {
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// CookieOptions provides security config for cookies
|
||||
type CookieOptions struct {
|
||||
SameSite http.SameSite // cookie SameSite property
|
||||
Secure bool // cookie Secure property
|
||||
HttpOnly bool // cookie HttpOnly property
|
||||
}
|
||||
|
||||
// cookieItem is the item stored in Cookie.
|
||||
type cookieItem struct {
|
||||
*http.Cookie // Underlying cookie items.
|
||||
@ -88,24 +95,31 @@ func (c *Cookie) Set(key, value string) {
|
||||
c.request.Server.GetCookieDomain(),
|
||||
c.request.Server.GetCookiePath(),
|
||||
c.request.Server.GetCookieMaxAge(),
|
||||
CookieOptions{
|
||||
SameSite: c.request.Server.GetCookieSameSite(),
|
||||
Secure: c.request.Server.GetCookieSecure(),
|
||||
HttpOnly: c.request.Server.GetCookieHttpOnly(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// SetCookie sets cookie item with given domain, path and expiration age.
|
||||
// The optional parameter `httpOnly` specifies if the cookie item is only available in HTTP,
|
||||
// The optional parameter `options` specifies extra security configurations,
|
||||
// which is usually empty.
|
||||
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) {
|
||||
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, options ...CookieOptions) {
|
||||
c.init()
|
||||
isHttpOnly := false
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
config := CookieOptions{}
|
||||
if len(options) > 0 {
|
||||
config = options[0]
|
||||
}
|
||||
httpCookie := &http.Cookie{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
HttpOnly: isHttpOnly,
|
||||
HttpOnly: config.HttpOnly,
|
||||
SameSite: config.SameSite,
|
||||
Secure: config.Secure,
|
||||
}
|
||||
if maxAge != 0 {
|
||||
httpCookie.Expires = time.Now().Add(maxAge)
|
||||
@ -136,6 +150,11 @@ func (c *Cookie) SetSessionId(id string) {
|
||||
c.request.Server.GetCookieDomain(),
|
||||
c.request.Server.GetCookiePath(),
|
||||
c.server.GetSessionCookieMaxAge(),
|
||||
CookieOptions{
|
||||
SameSite: c.request.Server.GetCookieSameSite(),
|
||||
Secure: c.request.Server.GetCookieSecure(),
|
||||
HttpOnly: c.request.Server.GetCookieHttpOnly(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,9 @@ func Test_ConfigFromMap(t *testing.T) {
|
||||
"indexFiles": g.Slice{"index.php", "main.php"},
|
||||
"errorLogEnabled": true,
|
||||
"cookieMaxAge": "1y",
|
||||
"cookieSameSite": "lax",
|
||||
"cookieSecure": true,
|
||||
"cookieHttpOnly": true,
|
||||
}
|
||||
config, err := ghttp.ConfigFromMap(m)
|
||||
t.Assert(err, nil)
|
||||
@ -39,6 +42,9 @@ func Test_ConfigFromMap(t *testing.T) {
|
||||
t.Assert(config.CookieMaxAge, d2)
|
||||
t.Assert(config.IndexFiles, m["indexFiles"])
|
||||
t.Assert(config.ErrorLogEnabled, m["errorLogEnabled"])
|
||||
t.Assert(config.CookieSameSite, m["cookieSameSite"])
|
||||
t.Assert(config.CookieSecure, m["cookieSecure"])
|
||||
t.Assert(config.CookieHttpOnly, m["cookieHttpOnly"])
|
||||
})
|
||||
}
|
||||
|
||||
@ -55,6 +61,9 @@ func Test_SetConfigWithMap(t *testing.T) {
|
||||
"SessionIdName": "MySessionId",
|
||||
"SessionPath": "/tmp/MySessionStoragePath",
|
||||
"SessionMaxAge": 24 * time.Hour,
|
||||
"cookieSameSite": "lax",
|
||||
"cookieSecure": true,
|
||||
"cookieHttpOnly": true,
|
||||
}
|
||||
s := g.Server()
|
||||
err := s.SetConfigWithMap(m)
|
||||
|
||||
@ -9,6 +9,7 @@ package ghttp_test
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -101,3 +102,67 @@ func Test_SetHttpCookie(t *testing.T) {
|
||||
//t.Assert(client.GetContent(ctx, "/get?k=key2"), "200")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CookieOptionsDefault(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Cookie.Set(r.Get("k").String(), r.Get("v").String())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
r1, e1 := client.Get(ctx, "/test?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
|
||||
t.Assert(e1, nil)
|
||||
t.Assert(r1.ReadAllString(), "")
|
||||
|
||||
parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ")
|
||||
|
||||
t.AssertIN(len(parts), []int{3, 4}) // For go < 1.16 cookie always output "SameSite", see: https://github.com/golang/go/commit/542693e00529fbb4248fac614ece68b127a5ec4d
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CookieOptions(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.SetConfigWithMap(g.Map{
|
||||
"cookieSameSite": "lax",
|
||||
"cookieSecure": true,
|
||||
"cookieHttpOnly": true,
|
||||
})
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Cookie.Set(r.Get("k").String(), r.Get("v").String())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
r1, e1 := client.Get(ctx, "/test?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
|
||||
t.Assert(e1, nil)
|
||||
t.Assert(r1.ReadAllString(), "")
|
||||
|
||||
parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ")
|
||||
|
||||
t.AssertEQ(len(parts), 6)
|
||||
t.Assert(parts[3], "HttpOnly")
|
||||
t.Assert(parts[4], "Secure")
|
||||
t.Assert(parts[5], "SameSite=Lax")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user