go jwt


jwt 的应用场景

单点登录(SSO)

JWT(JSON Web Tokens)在单点登录(SSO)系统中的应用可以更通用地描述为:

在传统的基于会话(session)或 cookie 的认证系统中,如果用户访问两个不同的域名,比如 A.cn 和 B.cn,他们会分别接收到两个不同的 cookie。这意味着,用户无法使用 A.cn 的 cookie 来通过 B.cn 的认证,因为每个域名都有自己独立的认证机制和会话存储。

然而,在使用 JWT 的场景中,认证过程变得更加统一和简便。JWT 为用户提供了一个统一的 token,这个 token 在不同的域名间是可共享和可验证的。换句话说,用户只需要获得一次认证,即可访问所有接入单点登录系统的应用,而无需针对每个应用分别登录。

即使你选择使用 cookie 或 session 来存储这个 JWT,这也不会影响其作为单点登录解决方案的有效性。JWT 本身是一个独立的、可在不同服务间共享的认证凭证,它包含了用户身份验证的所有必要信息,并且可以被多个不同的服务器独立验证,从而实现了跨域名的单点登录功能。

是什么

JWT是一种用于在认证之后传输信息的开放标准。它可以用于跨域信息交换和取代Session会话管理。

一个标准的 jwt 由三个对象组成:

  • Header:头部
  • Payload:负载
  • Signature:签名

先知道这三个东西,接下来会逐一进行解释。

我们从最简单的 Payload 开始讲。

Payload

这是一个 payload 的 demo

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

很显然,这个部分就是用于我们要在 jwt 中存储的信息,它没有什么很特别的地方,它的功能就是用于存储信息。

需要注意的是,由于 jwt 默认是不加密的,因此请勿在 jwt 中存放私密信息。

JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

当然你也可以设置自己的字段,在一开始的 demo 中就设置了两个自定义字段。

payload 部分只是一个 JSON 对象,用于存储所需信息。如果有必要可以采用其他格式,但建议使用JSON因为:

  1. JSON 是比较通用的轻量级数据交换格式,易于解析。

    jwt 其实是不易阅读的(指不能一步到位),给你们看个 jwt

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    就问你能看懂不?

    因为这玩意是经过 base64 编码的,要变成这样能阅读的格式还需要再解码一次。

    Header:

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

    Payload:

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    } 

    Signature:

    TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  2. 使用 JSON 可以使 JWT 本身也是自描述的,便于调试和理解。

  3. JSON 库在各种语言和平台上广泛支持。

  4. JSON 格式化的 Payload 易于在 URL 中传输(JWT 可以在 URL 的 Hash 部分或者 POST 请求体中传输,包括但不限于 URL。)。

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

把用户信息存储到本地其实是一种比较危险的操作,如果你使用的系统后端校验比较少,那么对 jwt 的修改可能就被直接通过,从而实现提权。举个栗子:上面 demo 中有个字段admin 普通用户通过手动修改此字段为 true,同时后端的校验比较简陋,那么会发生什么不用我说了吧。

为了防止 jwt 被修改,需要对其进行加密。

Signature部分是JWT中用于验证和保护消息完整性的部分。它是经过加密后的校验数据,通过重新计算提交上的数据和 signature 是否符合来判断 jwt 是否被修改。

这是一个运算后的 signature

6iGp-vOgdXhc3HcFDmvrk2G_HJQZ-wGku02eTvhKOYs

它是由下面四部分进行运算得到的:

  1. 加密算法(alg): 如HMAC SHA256或RSA 。
  2. Header
  3. payload
  4. 密钥(secret key):该密钥必须始终保密。

运算过程(demo)

HMACSHA256(
  base64UrlEncode(Header) + "." +
  base64UrlEncode(Payload),
  "secretkey" ) 
= 6iGp-vOgdXhc3HcFDmvrk2G_HJQZ-wGku02eTvhKOYs

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

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

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

缺点

  1. 有效载荷易于解析:JWT 的有效载荷(Payload)部分是以 Base64 编码字符串的形式进行传输,这意味着任何人都可以解码它并读取其内容。所以不要放置敏感数据在 Payload 中。
  2. 签名易于解析:JWT 的签名也是以 Base64 编码字符串的形式进行传输,这意味着如果使用对称加密算法(如 HS256),任何人都可以解码签名部分并破解密钥。所以对称加密算法只适用于测试环境,生产环境建议使用非对称加密算法(如 RS256)。
  3. 无法撤销:一旦 JWT 发布,它就不能在过期时间之前被撤销,这是由于 JWT 的无状态性质造成的。所以一些需要撤销功能的场景不适合使用 JWT,需要使用会话来实现。
  4. 加密开销较大:由于 JWT 需要加密和签名,这需要计算资源,这可能会带来一定的性能损耗。当然,这也要根据具体使用场景来定,对性能要求不高的应用影响不大。

快速上手

此处以 go 作为演示语言来实现 jwt

安装

go get github.com/golang-jwt/jwt

此版本是现在社区维护的一个较为好用的 jwt 库

前身是:github.com/dgrijalva/jwt-go

接下来我们就按照学习 jwt 的过程来构建一个基本的 jwt。

demo

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt"
)

func main() {
	//1. 设定一份密钥,需要好好存储
	key := []byte("(*≧︶≦))( ̄▽ ̄* )ゞ")
	//2. 设置 payload ,此包中是 claims
	//推荐使用 NewWithClaims 其
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"name": "test1",
		"role": "admin",
	})
	//也可以直接使用官方给的默认 claims 进行快速解析
	//token:=jwt.NewWithClaims(jwt.SigningMethodHS256,jwt.StandardClaims{
	//	Audience:  "",
	//	ExpiresAt: 0,
	//	Id:        "",
	//	IssuedAt:  0,
	//	Issuer:    "",
	//	NotBefore: 0,
	//	Subject:   "",
	//})
	//根据需求可以自行添加 header
	//token.Header["test"] = "test"
	s, _ := token.SignedString(key)
	fmt.Println(s)
}

参考资料


如果本文帮助到了你,帮我点个广告可以咩(o′┏▽┓`o)


文章作者: Anubis
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Anubis !
评论
  目录