Go Gin での JWT の使用

1.JWT

JWT の正式名称は JSON Web Token であり、クロスドメイン認証ソリューションであり、トークンの実装方法を規定するオープンスタンダードであり、現在フロントエンドとバックエンドの分離プロジェクトや OAuth2.0 のビジネスシナリオで使用されています。 。

2. ジンで JWT を使用する必要があるのはなぜですか?

従来の Cookie-Sesson モードはサーバー メモリを占有し、拡張性が低いため、クラスターまたはサービス間の検証シナリオが発生した場合は、Sesson レプリケーションまたはセッション永続性をサポートする必要があります。

1.JWTの基本原則

サーバー検証後、ユーザー情報のJSONを取得

1

2

3

4

5

{

     "user_id": "xxxxxx",

    "role": "xxxxxx",

    "refresh_token": "xxxxx"

}

(1) JWT TOKENの作成方法

JWT は非常に長い文字列です

eyJhbGciOiJI123afasrwrqqewrcCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6y JV_adQssw5c

3 つの部分で構成されており、各部分はドット (.) で区切られており、3 つの部分は次のとおりです。

 

  • ヘッダ
  • ペイロード
  • サイン

1)ヘッダー

ヘッダーはBASE64URLアルゴリズムで暗号化されたJSONオブジェクトで、復号化すると以下のようになります。

1

2

3

4

{

  "alg": "HS256",

  "typ": "JWT"

}

このうち、alg 属性は署名に使用されるアルゴリズムを示し、デフォルトは HS256 です。

typ は現在のトークンのタイプを表し、JWT のタイプは jwt です。

Base64URL

BASE64 と同様に、記号 +、/、= は URL 内で特別な意味を持つため、BASE64RUL アルゴリズムはこれらの記号を置き換えます。

+ -> -

= -> 無視される

/ -> _

2)ペイロード

ペイロード部分も BASE64URL アルゴリズムによって文字列に変換された JSON オブジェクトであり、ペイロード部分には 7 つの基本フィールドが含まれています。

  • iss (発行者): 発行者
  • exp (有効期限): 有効期限
  • sub(件名):件名
  • aud (聴衆): 聴衆
  • nbf (Not Before): 有効時間
  • iat (発行時刻): 発行時刻
  • jti (JWT ID): 番号

次のように、カスタム ビジネス フィールドを挿入することもできます。

ユーザーID

役割

3)署名

署名部分は、データの改ざんを防ぐための最初の 2 つの部分の署名です

まず、シークレットを指定する必要があります。このキーはサーバーのみが知っており、ユーザーに漏洩することはできません。次に、ヘッダーで指定された署名アルゴリズム (デフォルトは HMAC SHA256) を使用して、次の式に従って署名を生成します。

HMACSHA256(
  base64UrlEncode(ヘッダー) + "." +
  Base64UrlEncode(ペイロード)、
  シークレット)

(2) 復号化処理

システムは TOKEN を受け取ると、Header と Payload の文字列を取り出し、それらを結合した後、Header で指定されたハッシュ方式を使用して式を渡します。

HMACSHA256(
  base64UrlEncode(ヘッダー) + "." +
  Base64UrlEncode(ペイロード)、
  シークレット)

暗号化して暗号文を取得する

次に、取得した暗号文と TOKEN で渡された暗号文を比較し、同じであれば暗号文は変更されていないことを意味します。

3. JWTの特徴(メリット・デメリット)

  • JWT はデフォルトでは暗号化されませんが、暗号化することもできます。元のトークンを生成した後、キーを使用して再度暗号化できます。
  • 機密データは、暗号化せずに JWT に書き込むことはできません。
  • JWTは認証だけでなく情報のやり取りにも利用できます。JWT を効果的に使用すると、サーバーがデータベースにクエリを実行する回数を減らすことができます。
  • JWT の最大の欠点は、サーバーがセッション状態を保存しないため、使用中にトークンを取り消したり、トークンの権限を変更したりできないことです。つまり、JWT が発行されると、サーバーが追加のロジックをデプロイしない限り、有効期限が切れるまで有効なままになります。
  • JWT 自体には認証情報が含まれており、一度漏洩すると誰でもトークンのすべての権限を取得できます。盗難を減らすために、JWT の有効期間は比較的短く設定する必要があります。より重要な権限については、ユーザーが使用するときに再度認証する必要があります。
  • 盗難を減らすために、JWT は HTTP プロトコルを使用してそのまま送信するのではなく、HTTPS プロトコルを使用して送信する必要があります。

1.GIN は JWT を統合します

1

2

go get -u github.com/dgrijalva/jwt-go

go get github.com/gin-gonic/gin

jwtutil を書く

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

var Secret = []byte("whatasecret")

// jwt过期时间, 按照实际环境设置

const expiration = 2 * time.Minute

type Claims struct {

    // 自定义字段, 可以存在用户名, 用户ID, 用户角色等等

    Username string

    // jwt.StandardClaims包含了官方定义的字段

    jwt.StandardClaims

}

func GenToken(username string) (string, error) {

    // 创建声明

    a := &Claims{

        Username: username,

        StandardClaims: jwt.StandardClaims{

            ExpiresAt: time.Now().Add(expiration).Unix(), // 过期时间

            IssuedAt:  time.Now().Unix(),                 // 签发时间

            Issuer:    "gin-jwt-demo",                    // 签发者

            Id:        "",                                // 按需求选这个, 有些实现中, 会控制这个ID是不是在黑/白名单来判断是否还有效

            NotBefore: 0,                                 // 生效起始时间

            Subject:   "",                                // 主题

        },

    }

    // 用指定的哈希方法创建签名对象

    tt := jwt.NewWithClaims(jwt.SigningMethodHS256, a)

    // 用上面的声明和签名对象签名字符串token

    // 1. 先对Header和PayLoad进行Base64URL转换

    // 2. Header和PayLoadBase64URL转换后的字符串用.拼接在一起

    // 3. 用secret对拼接在一起之后的字符串进行HASH加密

    // 4. 连在一起返回

    return tt.SignedString(Secret)

}

func ParseToken(tokenStr string) (*Claims, error) {

    // 第三个参数: 提供一个回调函数用于提供要选择的秘钥, 回调函数里面的token参数,是已经解析但未验证的,可以根据token里面的值做一些逻辑, 如`kid`的判断

    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {

        return Secret, nil

    })

    if err != nil {

        return nil, err

    }

    // 校验token

    if claims, ok := token.Claims.(*Claims); ok && token.Valid {

        return claims, nil

    }

    return nil, errors.New("invalid token")

}

  • Secret は、暗号化された署名に使用される秘密キーです。
  • 有効期限はトークンの有効期限です
  • Claims は、カスタム フィールドと JWT によって指定されたフィールドを含む署名クレーム オブジェクトです。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

type Claims struct {

    // 自定义字段, 可以存在用户名, 用户ID, 用户角色等等

    Username string

    // jwt.StandardClaims包含了官方定义的字段

    jwt.StandardClaims

}

type StandardClaims struct {

    Audience  string `json:"aud,omitempty"`

    ExpiresAt int64  `json:"exp,omitempty"`

    Id        string `json:"jti,omitempty"`

    IssuedAt  int64  `json:"iat,omitempty"`

    Issuer    string `json:"iss,omitempty"`

    NotBefore int64  `json:"nbf,omitempty"`

    Subject   string `json:"sub,omitempty"`

}

(1)GenTokenメソッド

GenToken メソッドは、毎回異なる特定のユーザー名のトークンを生成します。

jwt.NewWithClaims(jwt.SigningMethodHS256, a) は署名オブジェクトを宣言し、HS256 のハッシュ アルゴリズムを指定します

token.SignedString(Secret) は、宣言されたオブジェクトと SECRET が、次のプロセスを含む、指定されたハッシュ アルゴリズムを使用して暗号化されることを示します。

  • まずヘッダーとペイロードで Base64URL 変換を実行します
  • 変換されたHeaderとPayLoadBase64URLの文字列が で結合されます。
  • シークレットを使用して、結合された文字列をハッシュします
  • BASE64URL(Header).BASE64URL(Payload).signature は連結された文字列として返されます。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

func GenToken(username string) (string, error) {

    // 创建声明

    a := &Claims{

        Username: username,

        StandardClaims: jwt.StandardClaims{

            ExpiresAt: time.Now().Add(expiration).Unix(), // 过期时间

            IssuedAt:  time.Now().Unix(),                 // 签发时间

            Issuer:    "gin-jwt-demo",                    // 签发者

            Id:        "",                                // 按需求选这个, 有些实现中, 会控制这个ID是不是在黑/白名单来判断是否还有效

            NotBefore: 0,                                 // 生效起始时间

            Subject:   "",                                // 主题

        },

    }

    // 用指定的哈希方法创建签名对象

    tt := jwt.NewWithClaims(jwt.SigningMethodHS256, a)

    // 用上面的声明和签名对象签名字符串token

    // 1. 先对Header和PayLoad进行Base64URL转换

    // 2. Header和PayLoadBase64URL转换后的字符串用.拼接在一起

    // 3. 用secret对拼接在一起之后的字符串进行HASH加密

    // 4. 连在一起返回

    return tt.SignedString(Secret)

}

(2) ParseTokenメソッド

ParseToken メソッドは、トークンを解析し、トークンが有効かどうかを検証します。

jwt.ParseWithClaims メソッド。Token の解析に使用され、その 3 番目のパラメーターは次のとおりです。

選択される秘密キーを提供するコールバック関数を提供します。コールバック関数のトークン パラメーターは解析されましたが、検証されていません。子供が別のシークレットを選択するかどうかを判断するなど、トークンの値に基づいていくつかのロジックを実行できます。

KID (オプション): 秘密キーのシリアル番号を表します。開発者はこれを使用して、認証トークンの特定の秘密キーを識別できます。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

func ParseToken(tokenStr string) (*Claims, error) {

    // 第三个参数: 提供一个回调函数用于提供要选择的秘钥, 回调函数里面的token参数,是已经解析但未验证的,可以根据token里面的值做一些逻辑, 如`kid`的判断

    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {

        return Secret, nil

    })

    if err != nil {

        return nil, err

    }

    // 校验token

    if claims, ok := token.Claims.(*Claims); ok && token.Valid {

        return claims, nil

    }

    return nil, errors.New("invalid token")

}

ミドルウェアを書く

ヘッダーから認可を削除し、それを使用して jwt.ParseToken を解析します。

トークンが変更されているかどうか、および有効期限が切れているかどうかを確認する

トークンから有効な情報を取得し、コンテキストに設定します。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

func JWTAuthMiddleware() func(ctx *gin.Context) {

    return func(ctx *gin.Context) {

        // 根据实际情况取TOKEN, 这里从request header取

        tokenStr := ctx.Request.Header.Get("Authorization")

        if tokenStr == "" {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_NULL,

                "msg":  code.GetMsg(code.ERR_AUTH_NULL),

            })

            return

        }

        claims, err := jwt.ParseToken(tokenStr)

        if err != nil {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_INVALID,

                "msg":  code.GetMsg(code.ERR_AUTH_INVALID),

            })

            return

        } else if time.Now().Unix() > claims.ExpiresAt {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_EXPIRED,

                "msg":  code.GetMsg(code.ERR_AUTH_EXPIRED),

            })

            return

        }

        // 此处已经通过了, 可以把Claims中的有效信息拿出来放入上下文使用

        ctx.Set("username", claims.Username)

        ctx.Next()

    }

}

ミドルウェアを使用する

/login不用中间件

中间件指定在authorizedrouter, 因此authorized下的所有路由都会使用此中间件

1

2

3

4

5

6

7

8

9

10

func main() {

    r := gin.Default()

    r.POST("/login", router.Login)

    authorized := r.Group("/auth")

    authorized.Use(jwt.JWTAuthMiddleware())

    {

        authorized.GET("/getUserInfo", router.GetUserInfo)

    }

    r.Run(":8082")

}

测试

login请求获取token

POST http://localhost:8082/login

 

把token放入getUserInfo请求

GET  http://localhost:8082/auth/getUserInfo

 

其他

完整的JWT登录还应该包括

  • 使TOKEN失效(过期或者黑名单等功能)
  • refresh token

おすすめ

転載: blog.csdn.net/u013302168/article/details/132178429