JWT 小白初识和使用

JWT就是    JSON Web Tokens的缩写。

首先说它与session的区别:

  1. session 存储在服务端占用服务器资源,而 JWT 存储在客户端
  2. session 存储在 Cookie 中,存在伪造跨站请求伪造攻击的风险
  3. session 只存在一台服务器上,那么下次请求就必须请求这台服务器,不利于分布式应用
  4. 存储在客户端的 JWT 比存储在服务端的 session 更具有扩展性
  5. 。。。。。。。。
  6. .。。。。。。。。嘿嘿 读者自己找。

JWT 标准的 Token 包含三个部分:

  • header(头部)
  • payload(数据)
  • signature(签名)

三部分一般会用点分隔开,且会用base64编码进行对称加密,所以真正的token例如:

JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTBBJTIwJTIyaXNzJTIyJTNBJTIwJTIycm9uZ2hvbmcubmV0JTIyJTJDJTBBJTIwJTIyZXhwJTIyJTNBJTIwJTIyMTQzODk1NTQ0NSUyMiUyQyUwQSUyMCUyMm5hbWUlMjIlM0ElMjAlMjJob25naG9uZyUyMiUyQyUwQSUyMCUyMmFkbWluJTIyJTNBJTIwdHJ1ZSUwQSU3RA==.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U

注(base64加密一般很多时候最后回事“= ”   这是我之前写小爬的时候的经验)


Header

每一个JWT token里面都有一个header,也就是头部数据。里面的主要内容就是:1.这是什么typ(JWT)/2.使用了什么算法alg(HS256)如果未使用加密可为none

JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=

解密后(通过base64解密):

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

Payload


payload里面是Token的具体内容,这些内容里面有一些标准字段,你也可以添加其他需要的内容。下面是标准字段:

  • iss:Issuer,发行者
  • sub:Subject,主题
  • aud:Audience,观众
  • exp:Expiration time,过期时间
  • nbf:Not before
  • iat:Issued at,发行时间
  • jti:JWT ID

比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间这两个标准字段。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。

{
 "iss": "ronghong.net",
 "exp": "1438955445",
 "name": "honghong",
 "admin": true
}

使用 base64url 编码以后就变成了这个样子:

JTdCJTBBJTIwJTIyaXNzJTIyJTNBJTIwJTIycm9uZ2hvbmcubmV0JTIyJTJDJTBBJTIwJTIyZXhwJTIyJTNBJTIwJTIyMTQzODk1NTQ0NSUyMiUyQyUwQSUyMCUyMm5hbWUlMjIlM0ElMjAlMjJob25naG9uZyUyMiUyQyUwQSUyMCUyMmFkbWluJTIyJTNBJTIwdHJ1ZSUwQSU3RA==

Signature

JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

也就是上面最开始我给的那个字符串,通过“.”分隔开的其中最后一部分的内容


  

服务端会验证token,如果验证通过就会返回相应的资源,整个流程就是这样:

后续 还会写入  JWT实际web编程的小demo,读者可以等我更新

想提前看到可以点这儿jwt运用

go语言的运用:

jwt.go   jwt_test.go:

jwt.go:

package main

import (
	// "fmt"
	"encoding/json"
	"encoding/base64"
	"crypto/sha256"
	"crypto/hmac"
	"encoding/hex"
	"strings"
)

// SALT 密钥
const SALT = "secret"

// Header 消息头部
type Header struct {
	Alg string `json:"alg"`
	Typ string `json:"typ"`
}
 
// PayLoad
type PayLoad struct {
	Sub string `json:"sub"`
	Name string `json:"name"`
	Admin bool `json:"admin"`
	// Expire int `json:"exp"`
}

// JWT 完整版
type JWT struct {
	Header	`json:"header"`
	PayLoad	`json:"payload"`
	Signature string `json:"signature"`
}

func main(){}

// Encode 将 json 转成符合 JWT 标准的字符串
func (jwt *JWT) Encode() string {
	header, err := json.Marshal(jwt.Header)
	checkError(err)
	headerString := base64.StdEncoding.EncodeToString(header)
	payload, err := json.Marshal(jwt.PayLoad)
	payloadString := base64.StdEncoding.EncodeToString(payload)
	checkError(err)
	
	format := headerString + "." + payloadString
    signature := getHmacCode(format)

	return format + "." + signature
}
//sha256包实现了SHA224和SHA256哈希算法
/*
func New               (1)
func New() hash.Hash
返回一个新的使用SHA256校验算法的hash.Hash接口。
------------------------------------------------------------------------------------------
func New               (2)
func New(h func() hash.Hash, key []byte) hash.Hash
New函数返回一个采用hash.Hash作为底层hash接口、key作为密钥的HMAC算法的hash接口。
-------------------------------------------------------------------------------------------
type Hash              (3)
type Hash interface {
    // 通过嵌入的匿名io.Writer接口的Write方法向hash中添加更多数据,永远不返回错误
    io.Writer
    // 返回添加b到当前的hash值后的新切片,不会改变底层的hash状态
    Sum(b []byte) []byte
    // 重设hash为无数据输入的状态
    Reset()
    // 返回Sum会返回的切片的长度
    Size() int
    // 返回hash底层的块大小;Write方法可以接受任何大小的数据,
    // 但提供的数据是块大小的倍数时效率更高
    BlockSize() int
}
Hash是一个被所有hash函数实现的公共接口。
-----------------------------------------------------------------------------------------
func EncodeToString     (4)
func EncodeToString(src []byte) string
将数据src编码为字符串s。
*/
func getHmacCode(s string) string {
    h := hmac.New(sha256.New, []byte(SALT))//(1) (2)
	h.Write([]byte(s))
	key := h.Sum(nil)// (3)
    return hex.EncodeToString(key)//(4)
}


// Decode 验证 jwt 签名是否正确,并将json内容解析出来
func (jwt *JWT) Decode( code string) bool {

	arr := strings.Split(code,".")
	if len(arr) != 3 {
		return false
	}

	// 验证签名是否正确
	format := arr[0] + "." + arr[1]
	signature := getHmacCode(format)
	if signature != arr[2] {
		return false
	}

	//逆转解码回去
	header, err := base64.StdEncoding.DecodeString(arr[0])
	checkError(err)
	payload, err := base64.StdEncoding.DecodeString(arr[1])
	checkError(err)

	
	json.Unmarshal(header, &jwt.Header)
	json.Unmarshal(payload,&jwt.PayLoad)

	return true
}

func checkError(err error) {
	if err != nil {
		panic(err)
	}
}

jwt_test.go:

package main

import (
	"testing"
)

func Test_Encode(t *testing.T) {
	jwt := JWT{}
	jwt.Header = Header{"HS256","JWT"}
	jwt.PayLoad = PayLoad{"1234567890","John Doe",true}
	result := jwt.Encode()
	t.Log(result)
}

func Test_Decode(t *testing.T) {
	testStr := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.4c9540f793ab33b13670169bdf444c1eb1c37047f18e861981e14e34587b1e04"

	jwt := JWT{}
	if jwt.Decode(testStr) {
		t.Log(jwt)
	} else {
		t.Error("error json content")
	}
}


增强jwt安全性方法:

  1. 缩短 token 有效时间
  2. 使用安全系数高的加密算法
  3. token 不要放在 Cookie 中,有 CSRF 风险
  4. 使用 HTTPS 加密协议
  5. 对标准字段 iss、sub、aud、nbf、exp 进行校验
  6. 使用成熟的开源库,不要手贱造轮子
  7. 特殊场景下可以把用户的 UA、IP 放进 payload 进行校验(不推荐)

参考理解:

https://www.cnblogs.com/yibutian/p/9507866.html

https://www.cnblogs.com/yibutian/p/9508018.html

https://www.cnblogs.com/yibutian/p/9510339.html

猜你喜欢

转载自blog.csdn.net/qq_40417296/article/details/83790714