研究下 jwt 相关的流程和逻辑是什么
jwt 是一种认证方式,适合分布式 sso 场景。服务端无需记录登录信息。
研究下 jwt 相关的流程和逻辑是什么
jwt 是一种认证方式,适合分布式 sso 场景。服务端无需记录登录信息。
| package main | |
| import ( | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "log" | |
| "net/http" | |
| "time" | |
| "github.com/golang-jwt/jwt" | |
| ) | |
| type Message struct { | |
| Status string `json:"status"` | |
| Info string `json:"info"` | |
| } | |
| // 这里需要 64 位的字符串,我自己乱写的,可以自己生成一个 64 长度的字符串 | |
| // var secretKey ed25519.PrivateKey = []byte("go-jwt1111111111111111111111111111111111111111111111111111111111") | |
| var secretKey = []byte("go-jwt") | |
| var jwtExpireMinute int64 = 10 | |
| var ( | |
| PrivateAuthHeaderName = "X-Private-Auth-Username" | |
| ) | |
| func generateJWT() (string, error) { | |
| token := jwt.New(jwt.SigningMethodHS256) | |
| claims := token.Claims.(jwt.MapClaims) | |
| // claims["exp"] = time.Now().Add(time.Duration(jwtExpireMinute) * time.Minute) | |
| // 这里需要根据 mapClaims 和 普通的 StandardClaims 区分开,另外 exp 的值需要和不同的 xxxClaims 匹配,map 就是需要是 float64 或者 json.Number | |
| /** | |
| type Claims struct { | |
| Username string `json:"username"` | |
| Password string `json:"password"` | |
| jwt.StandardClaims | |
| } | |
| */ | |
| claims["exp"] = float64(time.Now().Add(time.Duration(jwtExpireMinute) * time.Minute).Unix()) | |
| claims["authorized"] = true | |
| claims["user"] = "username" | |
| tokenString, err := token.SignedString(secretKey) | |
| if err != nil { | |
| log.Printf("generateJWT SignedString err: %+v", err) | |
| return "Sign Error", nil | |
| } | |
| return tokenString, nil | |
| } | |
| func handlePage(writer http.ResponseWriter, request *http.Request) { | |
| writer.Header().Set("Content-Type", "application/json") | |
| user := request.Header.Get(PrivateAuthHeaderName) | |
| var message Message | |
| if err := json.NewDecoder(request.Body).Decode(&message); err != nil { | |
| log.Printf("handlePage message json decode err: %+v", err) | |
| return | |
| } | |
| if err := json.NewEncoder(writer).Encode(message); err != nil { | |
| log.Printf("handlePage message json encode err: %+v", err) | |
| return | |
| } | |
| log.Println("success return message, ", user) | |
| } | |
| func verifyJWT(endpointHandler func(writer http.ResponseWriter, request *http.Request)) http.HandlerFunc { | |
| return func(writer http.ResponseWriter, request *http.Request) { | |
| if request.Header["Token"] != nil { | |
| token, err := jwt.Parse(request.Header["Token"][0], func(t *jwt.Token) (interface{}, error) { | |
| _, ok := t.Method.(*jwt.SigningMethodHMAC) | |
| if !ok { | |
| writer.WriteHeader(http.StatusUnauthorized) | |
| _, err := writer.Write([]byte("You're Unauthorized!")) | |
| if err != nil { | |
| log.Printf("token parse keyFunc StatusUnauthorized err:%+v", err) | |
| return nil, err | |
| } | |
| return "", nil | |
| } | |
| return secretKey, nil | |
| }) | |
| if err != nil { | |
| writer.WriteHeader(http.StatusUnauthorized) | |
| _, writerErr := writer.Write([]byte("You're Unauthorized due to error parsing the JWT")) | |
| if writerErr != nil { | |
| return | |
| } | |
| return | |
| } | |
| if !token.Valid { | |
| writer.WriteHeader(http.StatusUnauthorized) | |
| _, err := writer.Write([]byte("You're Unauthorized due to No token in the header")) | |
| if err != nil { | |
| return | |
| } | |
| return | |
| } | |
| if err := extractClaims(token, request); err != nil { | |
| writer.WriteHeader(http.StatusUnauthorized) | |
| _, err := writer.Write([]byte("You're Unauthorized due to token parse error in the header")) | |
| if err != nil { | |
| return | |
| } | |
| return | |
| } | |
| endpointHandler(writer, request) | |
| return | |
| } | |
| writer.WriteHeader(http.StatusUnauthorized) | |
| _, err := writer.Write([]byte("You're Unauthorized due to No token in the header")) | |
| if err != nil { | |
| return | |
| } | |
| } | |
| } | |
| func extractClaims(token *jwt.Token, request *http.Request) error { | |
| if token == nil { | |
| return errors.New("extractClaims with empty token") | |
| } | |
| if !token.Valid { | |
| return errors.New("extractClaims with invalid token") | |
| } | |
| claims, ok := token.Claims.(jwt.MapClaims) | |
| if !ok { | |
| return errors.New("extractClaims with invalid type token claims") | |
| } | |
| username := claims["user"].(string) | |
| request.Header.Add(PrivateAuthHeaderName, username) | |
| return nil | |
| } | |
| func auth(writer http.ResponseWriter, request *http.Request) { | |
| token, err := generateJWT() | |
| if err != nil { | |
| return | |
| } | |
| // client := &http.Client{} | |
| // request, _ := http.NewRequest("POST", "<http://localhost:8080/>", nil) | |
| request.Header.Set("Token", token) | |
| _, err = writer.Write([]byte(token)) | |
| if err != nil { | |
| return | |
| } | |
| // _, _ = client.Do(request) | |
| } | |
| func main() { | |
| http.HandleFunc("/home", verifyJWT(handlePage)) | |
| http.HandleFunc("/login", auth) | |
| serverPort := 8099 | |
| if err := http.ListenAndServe(fmt.Sprintf(":%v", serverPort), nil); err != nil { | |
| log.Println("There was an error listening on ", fmt.Sprintf(":%v", serverPort), err) | |
| return | |
| } | |
| } |
| go-getJWT-Token: | |
| curl --location '127.0.0.1:8099/login' | |
| go-testReq: | |
| curl --location '127.0.0.1:8099/home' \ | |
| --header 'Content-Type: application/json' \ | |
| --header 'Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDgyMjc0MDMsInVzZXIiOiJ1c2VybmFtZSJ9.aXMV7LxV7dEL_v8gtWqOl1gaesDkfIEQjfAJUZWFMdc' \ | |
| --data '{\ | |
| "status": "200",\ | |
| "info": "test"\ | |
| }' |