JWT Token認證

什麼是JWT?

JWT是JSON Web Token的縮寫,定義了一種簡介自暴寒的方法用於通訊雙方之間以Json對象的形式安全的傳遞信息。由於特定的數字簽名,因此這些通訊的信息可以被校驗和信任。 JWT能夠使用HMAC算法或者RSA的公鑰私鑰對進行簽名。git

讓咱們進一步的解釋下關於JWT的定義:github

  • 簡約(Compact): JWT通訊中使用的數據量比較小,JWT能夠經過URL、POST參數,或者直接在HTTP header進行傳遞。 而且,由於比較小的數據量,這也意味着傳輸速度會更加快速。
  • 自包含(Self-contained): 負載(payload)中(能夠)包含所需的全部用戶部分的信息,能夠避免對服務端數據庫的屢次查詢。

JWT組成

JWT由三部分組成,使用.分隔:golang

  • 頭部(Header)
  • 載荷(Payload)
  • 簽名(Signature)

因此,JWT最終樣式爲:XXXXX.XXXXX.XXXXXweb

Header

JWT的頭部包含描述該JWT的最基本的信息:token的類型, 如JWT, 以及token使用的加密算法, 如 HMAC SHA256或者RSA.。能夠被表示爲一個JSON對象:算法

{
    "typ": "JWT",
    "alg":    "HS256"
}

Payload

JWT token的第二部分是payload, Payload包含claims. Claims是一些實體(一般指用戶)的狀態信息和其餘元數據。數據庫

{
  "iss": "sysu", 
  "iat": 1233458243, 
  "exp": 1448333419, 
  "aud": "sysu-ss", 
  "sub": "sysu-ss", 
}

iss(issuer 簽發者),是否使用是可選的;
iat(issued at簽發時間),這裏是一個Unix時間戳,是否使用是可選的;
exp(expiration time 過時時間) ,這裏是一個Unix時間戳,是否使用是可選的;
aud(audience 接收方 ),是否使用是可選的;
sub(subject 面向的用戶),是否使用是可選的;json

關於更多有關Payload字段的介紹,請查看JWT官網安全

Signature

要建立簽名部分,須要使用通過編碼後的頭部(header)和負載(payload)以及一個密鑰,將header和payload用.鏈接起來後,使用header中制定的算法進行簽名。
該簽名是用戶驗證JWT的請求發送者以及確保數據信息在傳輸過程當中的消息是未經篡改的。app

JWT使用(golang)

我使用的是jwt-gonegroni web中間件以及mux搭建服務。關於這三個庫的使用請自行谷歌。函數

  • 首先定義本身的Token:
// service/jwt.go
var tokens []Token    // 做爲全局變量存儲登陸用戶的token,能夠存儲到數據庫中
const TokenName = "SW-TOKEN"
const Issuer = "Go-GraphQL-Group"
const SecretKey = "StarWars"

type Token struct {
    SW_TOKEN string `json:"SW-TOKEN"`
}

type jwtCustomClaims struct {
    jwt.StandardClaims

    Admin bool `json:"admin"`
}

func CreateToken(secretKey []byte, issuer string, isAdmin bool) (token Token, err error) {
    claims := &jwtCustomClaims{
        jwt.StandardClaims{
            ExpiresAt: int64(time.Now().Add(time.Hour * 1).Unix()),
            Issuer:    issuer,
        },
        isAdmin,
    }

    tokenStr, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretKey)
    token = Token{
        tokenStr,
    }
    return
}

func ParseToken(tokenStr string, secretKey []byte) (claims jwt.Claims, err error) {
    var token *jwt.Token
    token, err = jwt.Parse(tokenStr, func(*jwt.Token) (interface{}, error) {
        return secretKey, nil
    })
    claims = token.Claims
    return
}
  • 以後生成服務並註冊路由:
// main.go
func NewServer() *negroni.Negroni {
    router := mux.NewRouter()
    initRoutes(router)

    n := negroni.Classic()    // negroni.Classic() 返回帶有默認中間件的Negroni實例指針

    n.UseHandler(router)    // 將router中http.Handler加入到negroni的中間件棧中
    return n
}

func initRoutes(router *mux.Router) {
    router.HandleFunc("/login", service.LoginHandler).Methods("POST")
    // 使用中間件進行token認證
    router.Use(service.TokenMiddleware)
    // query服務
    router.HandleFunc("/query", handler.GraphQL(GraphQL_Service.NewExecutableSchema(GraphQL_Service.Config{Resolvers: &GraphQL_Service.Resolver{}})))
    // 退出路由
    router.HandleFunc("/logout", service.LogoutHandler).Methods("POST", "GET")
}
  • login路由處理函數簽發Token:
func LoginHandler(w http.ResponseWriter, r *http.Request) {
    err := r.ParseForm()
    fmt.Println(r.Form.Get("username"))
    if err != nil {
        w.WriteHeader(http.StatusForbidden)
        fmt.Fprint(w, "Error in request")
        return
    }
    // 使用固定的username和password作測試
    if strings.ToLower(r.Form.Get("username")) != "admin" || r.Form.Get("password") != "password" {
        w.WriteHeader(http.StatusForbidden)
        fmt.Println("Error logging in")
        fmt.Fprint(w, "Invalid credentials")
        return
    }

    token, err := CreateToken([]byte(SecretKey), Issuer, false)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error extracting the key")
        log.Fatal(err)
    }

    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")

    tokenBytes, err := json.Marshal(token)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error marshal the token")
        log.Fatal(err)
    }
    tokens = append(tokens, token)
    w.Write(tokenBytes)
}
  • 中間件定義以下:
func TokenMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 不是login的請求須要進行token認證
        if r.RequestURI[1:] != "login" {
            /*
                // token位於Authorization中,用此方法
                token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) {
                    return []byte(SecretKey), nil
                })
            */
            tokenStr := ""
            for k, v := range r.Header {
                if strings.ToUpper(k) == TokenName {
                    tokenStr = v[0]
                    break
                }
            }
            validToken := false
            for _, token := range tokens {
                if token.SW_TOKEN == tokenStr {
                    validToken = true
                }
            }
            if validToken {
                ctx := context.WithValue(r.Context(), TokenName, tokenStr)
                next.ServeHTTP(w, r.WithContext(ctx))
            } else {
                w.WriteHeader(http.StatusUnauthorized)
                w.Write([]byte("Unauthorized access to this resource"))
                //fmt.Fprint(w, "Unauthorized access to this resource")
            }
        } else {
            next.ServeHTTP(w, r)
        }
    })
}
  • logout路由處理函數(刪除token):
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
    tokenStr := ""
    for k, v := range r.Header {
        if strings.ToUpper(k) == TokenName {
            tokenStr = v[0]
            break
        }
    }
    for i, token := range tokens {
        if token.SW_TOKEN == tokenStr {
            tokens = append(tokens[:i], tokens[i+1:]...)
            break
        }
    }
    w.Write([]byte("logout"))
}
  • 啓動服務
// main.go
func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = defaultPort
    }

    server := NewServer()
    server.Run(":" + port)

    log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
}
  • 測試服務
  1. 未登陸,query

在這裏插入圖片描述

  1. 登陸login

在這裏插入圖片描述

  1. query

在這裏插入圖片描述

  1. 登出logout

在這裏插入圖片描述

  1. query

在這裏插入圖片描述

代碼源地址:Github

相關文章
相關標籤/搜索