From eb19b7e909b686c094b880b4efcfaa051cc42da2 Mon Sep 17 00:00:00 2001 From: fwqaaq Date: Tue, 2 Jul 2024 02:07:29 +0800 Subject: [PATCH] go jwt example --- http/go_example/go.mod | 1 + http/go_example/go.sum | 2 ++ http/go_example/jwt/main.go | 53 ++++++++++++++++++++++++++++++ http/go_example/jwt/mod/mod.go | 48 +++++++++++++++++++++++++++ http/go_example/jwt/mod/refresh.go | 52 +++++++++++++++++++++++++++++ http/go_example/jwt/mod/signin.go | 50 ++++++++++++++++++++++++++++ http/go_example/jwt/mod/welcom.go | 40 ++++++++++++++++++++++ http/http_authorized.md | 12 ++++++- 8 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 http/go_example/jwt/main.go create mode 100644 http/go_example/jwt/mod/mod.go create mode 100644 http/go_example/jwt/mod/refresh.go create mode 100644 http/go_example/jwt/mod/signin.go create mode 100644 http/go_example/jwt/mod/welcom.go diff --git a/http/go_example/go.mod b/http/go_example/go.mod index 100a55e..76c1661 100644 --- a/http/go_example/go.mod +++ b/http/go_example/go.mod @@ -3,6 +3,7 @@ module grpc_example go 1.21.3 require ( + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.3 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/http/go_example/go.sum b/http/go_example/go.sum index a721d36..bfa3296 100644 --- a/http/go_example/go.sum +++ b/http/go_example/go.sum @@ -1,3 +1,5 @@ +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= diff --git a/http/go_example/jwt/main.go b/http/go_example/jwt/main.go new file mode 100644 index 0000000..90919e1 --- /dev/null +++ b/http/go_example/jwt/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "grpc_example/jwt/mod" + "log" + "net/http" +) + +// import ( +// "fmt" +// "grpc_example/jwt/mod" +// "time" + +// "github.com/golang-jwt/jwt/v5" +// ) + +// type MyClaims struct { +// Name string +// Gender int +// Age int +// jwt.RegisteredClaims +// } + +// func main() { +// claims := MyClaims{ +// Name: "Jeremy", +// Gender: 1, +// Age: 18, +// RegisteredClaims: jwt.RegisteredClaims{ +// ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), +// IssuedAt: jwt.NewNumericDate(time.Now()), +// NotBefore: jwt.NewNumericDate(time.Now()), +// }, +// } + +// hs := mod.HS{ +// Key: "fwqaaq", +// } + +// sign, err := hs.Encode(claims) +// fmt.Println(sign, err) +// var outClaims MyClaims +// err = hs.Decode(sign, &outClaims) +// fmt.Println(outClaims, err) +// } + +func main() { + http.HandleFunc("/signin", mod.Signin) + http.HandleFunc("/welcome", mod.Welcome) + http.HandleFunc("/refresh", mod.Refresh) + + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/http/go_example/jwt/mod/mod.go b/http/go_example/jwt/mod/mod.go new file mode 100644 index 0000000..938d791 --- /dev/null +++ b/http/go_example/jwt/mod/mod.go @@ -0,0 +1,48 @@ +package mod + +import ( + "github.com/golang-jwt/jwt/v5" +) + +// Key struct +type Key struct { + Key []byte +} + +type Vaildator interface { + Encode(claims jwt.Claims) (string, error) + Decode(sign string, claims jwt.Claims) error +} + +func (k *Key) Encode(c jwt.Claims) (string, error) { + // Create the JWT token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + // Create a string from the token + sign, err := token.SignedString(k.Key) + return sign, err +} + +func (k *Key) Decode(sign string, c jwt.Claims) error { + _, err := jwt.ParseWithClaims(sign, c, func(t *jwt.Token) (interface{}, error) { + return k.Key, nil + }) + return err +} + +var users = map[string]string{ + "user": "password", +} + +var jwtKey = Key{ + Key: []byte("fwqaaq"), +} + +type Credentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type Claims struct { + Username string `json:"username"` + jwt.RegisteredClaims +} diff --git a/http/go_example/jwt/mod/refresh.go b/http/go_example/jwt/mod/refresh.go new file mode 100644 index 0000000..634dbd7 --- /dev/null +++ b/http/go_example/jwt/mod/refresh.go @@ -0,0 +1,52 @@ +package mod + +import ( + "net/http" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +func Refresh(w http.ResponseWriter, r *http.Request) { + c, err := r.Cookie("token") + if err != nil { + if err == http.ErrNoCookie { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.WriteHeader(http.StatusBadRequest) + return + } + + token := c.Value + claims := &Claims{} + + err = jwtKey.Decode(token, claims) + if err != nil { + if err == jwt.ErrSignatureInvalid { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.WriteHeader(http.StatusBadRequest) + return + } + + // Refresh the token when old token is going to expire(in the 30s before expiration) + if time.Until(claims.ExpiresAt.Time) < 30*time.Second { + w.WriteHeader(http.StatusAccepted) + return + } + + expirationTime := time.Now().Add(5 * time.Minute) + claims.ExpiresAt = jwt.NewNumericDate(expirationTime) + tokenString, err := jwtKey.Encode(claims) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: tokenString, + Expires: expirationTime, + }) +} diff --git a/http/go_example/jwt/mod/signin.go b/http/go_example/jwt/mod/signin.go new file mode 100644 index 0000000..c57d140 --- /dev/null +++ b/http/go_example/jwt/mod/signin.go @@ -0,0 +1,50 @@ +package mod + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +func Signin(w http.ResponseWriter, r *http.Request) { + var creds Credentials + // Decode the request body into the struct + err := json.NewDecoder(r.Body).Decode(&creds) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + expectedPassword, ok := users[creds.Username] + if !ok || expectedPassword != creds.Password { + w.WriteHeader(http.StatusUnauthorized) + return + } + + claims := &Claims{ + Username: creds.Username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + }, + } + + tokenString, err := jwtKey.Encode(claims) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: tokenString, + Expires: time.Now().Add(5 * time.Minute), + }) + json.NewEncoder(w).Encode(map[string]string{"message": "Successfully signed in"}) + +} diff --git a/http/go_example/jwt/mod/welcom.go b/http/go_example/jwt/mod/welcom.go new file mode 100644 index 0000000..fdb26c3 --- /dev/null +++ b/http/go_example/jwt/mod/welcom.go @@ -0,0 +1,40 @@ +package mod + +import ( + "net/http" + + "github.com/golang-jwt/jwt/v5" +) + +func Welcome(w http.ResponseWriter, r *http.Request) { + // Get the token from the cookie + c, err := r.Cookie("token") + if err != nil { + if err == http.ErrNoCookie { + // If the cookie is not set, return an unauthorized status + w.WriteHeader(http.StatusUnauthorized) + return + } + // Return bad request for other errors + w.WriteHeader(http.StatusBadRequest) + return + } + + token := c.Value + + claims := &Claims{} + + err = jwtKey.Decode(token, claims) + + if err != nil { + if err == jwt.ErrSignatureInvalid { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.WriteHeader(http.StatusBadRequest) + return + } + + w.Write([]byte("Welcome " + claims.Username)) + +} diff --git a/http/http_authorized.md b/http/http_authorized.md index c048028..60e1785 100644 --- a/http/http_authorized.md +++ b/http/http_authorized.md @@ -101,6 +101,17 @@ Session:浏览器和服务器是在进行会话,然而比较模糊的就是 参考: +查看示例:[Go JWT 示例](./go_example/jwt/main.go) + +```shell +# sign in +curl -v -X POST "http://localhost:8080/signin" -d '{"username": "user", "password": "password"}' --header "Content-Type: application/json" +# welcome +curl -b "cookie" http://localhost:8080/welcome +# refresh(在 30s 之内) +curl -v -b "cookie" http://localhost:8080/refresh +``` + ### 总结 1. Session 是由服务器诞生并且保存在服务器中的,由服务器主导 @@ -119,4 +130,3 @@ Session:浏览器和服务器是在进行会话,然而比较模糊的就是 如果在保存密码的时候是明文,那么撞库之后,用户密码信息将完全被破解。而当你使用 hash 运算,但是没有进行加密,那么攻击者在得知使用的是什么加密算法的情况下,使用**彩虹表**可以很快的破解密码。而加*盐*之后,由于 hash 运算是单向的,彩虹表很难或者根本收集不到密码的内容,很难进行逆向解析。 参考: -