会话管理
HTTP会话管理
HTTP 协议的特点是一问一答(请求然后响应)。基本上,Web 应用都实现了用户管理,因此当用户发送请求时,服务器要能识别出是哪个用户,最简单的方法就是客户端每次请求,都附上用户信息。这样既不安全也不高效,故提出会话(Session),会话一般存储用户信息。
服务端会话(Server Side Session)
会话内容存储在服务器中。服务器会给每个发送请求的客户端分配一个会话id(Session_id),来识别用户。用户发送请求时会附带会话id,服务器通过会话id 查询会话内容,根据会话内容给出服务。
客户端会话(Client Side Session)
会话内容存储在客户端中。客户端发送请求时会附带会话内容,服务器可以直接根据会话内容给出服务。第一次发送请求的新客户端,服务器会为其生成会话内容,并返给客户端。为了防止篡改,服务器会对生成的会话内容进行加密签名。
JWT
介绍
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。一个 JWT 通常有 HEADER (头),PAYLOAD (有效载荷)和 SIGNATURE (签名)三个部分组成,三者之间使用“.”链接。
JWT 标准只是设计了一个防篡改令牌,并非是为会话管理而设计,常用于客户端会话。
在 Go 语言可以使用github的 jwt 库来实现 JWT 的功能:
go get -u github.com/golang-jwt/jwt/v5
生成JWT
在jwt库中,生成JWT的函数是:NewWithClaims
func NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Token { return &Token{ Header: map[string]interface{}{ "typ": "JWT", "alg": method.Alg(), }, Claims: claims, Method: method, } }
从上述函数可以看到:JWT的Header 已经为我们处理好了,Payload (有效载荷)在库中被称为 Claims,而 Signature(签名)可以用库中现成的加密算法来签名,因此我们只需要编写我们想要的 Calims 就好。Claims 是一个接口:
type Claims interface { GetExpirationTime() (*NumericDate, error) GetIssuedAt() (*NumericDate, error) GetNotBefore() (*NumericDate, error) GetIssuer() (string, error) GetSubject() (string, error) GetAudience() (ClaimStrings, error) }
如果没有其他需求,我们可以直接使用 jwt 库给的 jwt.RegisteredClaims :
type RegisteredClaims struct { Issuer string `json:"iss,omitempty"` //JWT的签发者 Subject string `json:"sub,omitempty"` //JWT的所有者 Audience ClaimStrings `json:"aud,omitempty"` //JWT的接收者 ExpiresAt *NumericDate `json:"exp,omitempty"` //过期时间 NotBefore *NumericDate `json:"nbf,omitempty"` //生效时间 IssuedAt *NumericDate `json:"iat,omitempty"` //签发时间 ID string `json:"jti,omitempty"` //JWT标识 }
来个生成JWT例子:
package main import ( "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) type Myclaims struct { UserName string `json:"username"` PassWord string `json:"pwd"` jwt.RegisteredClaims // 继承jwt.RegisteredClaims } func createToken(user string, pwd string) (*jwt.Token, string, error) { claims := Myclaims{ //实例化claims UserName: user, PassWord: pwd, RegisteredClaims: jwt.RegisteredClaims{ Issuer: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)), // 5分钟后过期 NotBefore: jwt.NewNumericDate(time.Now()), // 签发后立即生效 }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 生成JWT,到这步还没完,还需要签名 tokenString, err := token.SignedString([]byte("secret")) // 签名 if err != nil { return token, "", err } return token, tokenString, err } func main() { r := gin.Default() r.POST("/login", func(c *gin.Context) { user := c.PostForm("username") pwd := c.PostForm("pwd") token, tokenString, _ := createToken(user, pwd) c.JSON(200, gin.H{ "没签名的JWT": token, "签名后的JWT": tokenString, }) }) r.Run("127.0.0.1:8000") }
效果:
除了jwt.NewWithClaims,jwt.New也可以生成JWT,只不过 New 只能携带实现了Claims 接口的 map[string]inferface{},所以一般都用 jwt.NewWithClaims:
func createToken2(user string, pwd string) (*jwt.Token, string, error) { token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) //设置claims claims["username"] = user claims["pwd"] = pwd claims["iss"] = "admin" claims["exp"] = jwt.NewNumericDate(time.Now().Add(5 * time.Minute)) claims["nbf"] = jwt.NewNumericDate(time.Now()) tokenString, err := token.SignedString([]byte("secret")) if err != nil { return token, "", err } return token, tokenString, err }
解析JWT
使用 jwt.ParseWithClaims 来解析JWT:
func ParseToken(tokenString string) (*Myclaims, error) { // 解析token token, err := jwt.ParseWithClaims(tokenString, &Myclaims{}, func(token *jwt.Token) (i interface{}, err error) { return []byte("secret"), nil }) if err != nil { return nil, err } return token.Claims.(*Myclaims), nil } func main() { r := gin.Default() r.POST("/test", func(c *gin.Context) { //获取json数据 data, _ := c.GetRawData() var body map[string]interface{} _ = json.Unmarshal(data, &body) //从 json 获取token token := body["token"] //解析token claims, err := ParseToken(token.(string)) if err != nil { c.JSON(200, gin.H{ "error": err.Error(), "接收的token": token, }) return } user := claims.UserName pwd := claims.PassWord c.JSON(200, gin.H{ "解析出的claims": claims, "提取出的username": user, "提取出的passwoed": pwd, }) }) r.Run("127.0.0.1:8000") }
效果:
(上面的token过期了)
关于JWT的操作一般放在中间件上去执行,而不是在路由里
Cookie-Session
介绍
服务端会话常用 Cookie-Session 模式实现用户认证,其相关流程大致如下:
- 用户在浏览器端填写用户名和密码,并发送给服务端
- 服务端对用户名和密码校验通过后会生成一份保存当前用户相关信息的 session 数据和一个与之对应的标识(通常称为session_id)
- 服务端返回响应时将上一步的 session_id 写入用户浏览器的 Cookie
- 后续用户来自该浏览器的每次请求都会自动携带包含 session_id 的 Cookie
- 服务端通过请求中的 session_id 就能找到之前保存的该用户那份 session 数据,从而获取该用户的相关信息。
type Cookie struct {
Name string
Value string
Path string //设置当前的 Cookie 值只有在访问指定路径时才能被服务器程序读取
Domain string //表达这个cookie是属于哪个网站的,默认值是当前正在访问的Host
的域名
Expires time.Time //过期时间,若为空则Cookie随浏览器关闭而销毁
RawExpires string
// MaxAge=0表示未设置Max-Age属性
// MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
// MaxAge>0表示存在Max-Age属性,单位是秒
MaxAge int
Secure bool //是否安全传输,设置后只能随https请求发送
HttpOnly bool //设置是否仅能用于传输
Raw string
Unparsed []string // 未解析的“属性-值”对的原始文本
}
设置Cookie
gin 提供函数支持设置Cookie:
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
其内容是调用标准库 net/http 中的 SetCookie函数:
func SetCookie(w ResponseWriter, cookie *Cookie) { if v := cookie.String(); v != "" { w.Header().Add("Set-Cookie", v) //向响应头添加Cookie } }
获取Cookie
获取Cookie有两种方法:
// 解析并返回该请求的Cookie头设置的所有cookie func (r *Request) Cookies() []*Cookie // 返回请求中名为name的cookie,如果未找到该cookie会返回nil, ErrNoCookie。 func (r *Request) Cookie(name string) (*Cookie, error)
例子
func main() { r := gin.Default()
r.GET("/setcookie", func(c *gin.Context) { //cookie名字为gin_cookie,内容是"设置一个cookie",允许/getcookie获取 c.SetCookie("gin_cookie", "设置一个cookie", 3600, "/", "/getcookie", false, true) }) r.GET("/getcookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") // 获取Cookie if err != nil { c.JSON(200, gin.H{ "getcookie_error": err.Error(), }) return } c.JSON(200, gin.H{ "get_cookie": cookie, }) }) r.Run("127.0.0.1:8000") }
效果:
标签:string,JWT,jwt,笔记,会话,token,Cookie,gin From: https://www.cnblogs.com/Owhy/p/17959974