在Golang語言中, jwt-go 庫提供了一些jwt編碼和驗證的工具,因此我們很容易使用該庫來實現token認證。
另外,我們也知道 gin 框架中支援使用者自定義middleware,我們可以很好的將jwt相關的邏輯封裝在middleware中,然後對具體的介面進行認證。
go get -u github.com/dgrijalva/jwt-go
又可將其稱作Payload,以Json的形式將使用者相關訊息,甚至是過期時間、簽證發放時間都寫在這
app/middleware/jwt-token.go
// MyClaims Customer jwt.StandardClaims
type MyClaims struct {
Account string `json:"account"`
jwt.StandardClaims
}
MyClaims
,並除了jwt-go
原本的jwt.StandardClaims
外,我們還另外儲存了Account的資訊再來我們會寫個產生Token的Function,用以產生用來認證的JWT Token。
app/middleware/jwt-token.go
// GenToken Create a new token
func GenToken(account string) (string, error) {
c := MyClaims{
account,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: "Flynn",
},
}
// Choose specific algorithm
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// Choose specific Signature
return token.SignedString(SecretKey)
}
接下來則是寫個Login function來讓Login API的Router做使用
app/controller/user.go
// AuthHandler @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @param register body Login true "login"
// @Success 200 string successful return token
// @Router /v1/users/login [post]
func (u UsersController) AuthHandler(c *gin.Context) {
var form Login
bindErr := c.BindJSON(&form)
if bindErr != nil {
c.JSON(http.StatusOK, gin.H{
"code": -1,
"msg": "Invalid params",
})
return
}
userOne, err := service.LoginOneUser(form.Account, form.Password)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": -1,
"msg": "Failed to parse params" + err.Error(),
"data": nil,
})
}
if userOne == nil {
c.JSON(http.StatusNotFound, gin.H{
"status": -1,
"msg": "User not found",
"data": nil,
})
}
if userOne != nil {
tokenString, _ := middleware.GenToken(form.Account)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "Success",
"data": gin.H{"token": tokenString},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "Verified Failed.",
})
return
}
再來則是寫一個驗證JWT Token的Middleware來給router使用
app/midddleware/jwt-token.go
// ParseToken Parse token
func ParseToken(tokenString string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return SecretKey, nil
})
if err != nil {
return nil, err
}
// Valid token
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// JWTAuthMiddleware Middleware of JWT
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// Get token from Header.Authorization field.
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": -1,
"msg": "Authorization is null in Header",
})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusUnauthorized, gin.H{
"code": -1,
"msg": "Format of Authorization is wrong",
})
c.Abort()
return
}
// parts[0] is Bearer, parts is token.
mc, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": -1,
"msg": "Invalid Token.",
})
c.Abort()
return
}
// Store Account info into Context
c.Set("account", mc.Account)
// After that, we can get Account info from c.Get("account")
c.Next()
}
}
gin.Context
的Header拿出Authorization token,接著就是對該Token進行驗證,並將驗證失敗的地方補上Response。app/config/route.go
posts.GET("/:id", middleware.JWTAuthMiddleware(), controller.QueryUsersController().GetUser)
JWTAuthMiddleware()
main.go
// @title Gin swagger
// @version 1.0
// @description Gin swagger
// @contact.name Flynn Sun
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// schemes http
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
BearerAuth
,產生新的swag template,這樣Swagger頁面才有地方讓人輸入Tokenapp/controller/user.go
// GetUser @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @Security BearerAuth
// @param id path int true "id" default(1)
// @Success 200 string successful return data
// @Router /v1/users/{id} [get]
func (u UsersController) GetUser(c *gin.Context) {
@Security BearerAuth
這行annotation,否則他會吃不到swagger的BearerAuth token最後則記得在專案根目錄輸入swag init
來重新產生swagger template,否則上面更改的swagger shit不會生效喔。
首先我們Login並取得有效token
再來,假設沒有輸入token就執行需要JWT驗證的API,就會獲得以下回應
因此我們需要到swagger頁面上方找到 Authorization Button,並輸入我們的token:
Bearer <token>
接著就可以再次地去執行需要JWT驗證的API了
這章節我們透過實戰來將jwt-go
與現有的後端程式碼進行結構,從middleware、router、controller再到swagger都實裝了一遍,如果有細節不懂者可以參考下方連結,這次的code我也會放在這。
https://github.com/Neskem/Ironman-2021/tree/Day-21