jwt教程

作者: sladeliu | 来源:发表于2017-10-13 13:06 被阅读0次

    未完待续

    JWT是什么?

    JWT是JSON Web Token的缩写,即JSON Web令牌

    <a href="https://tools.ietf.org/html/rfc7519" target="_blank">JWT规范</a>中对其所作的描述是:

    JSON Web令牌(JWT)是一种紧凑的、URL安全的方式,用来表示要在双方之间传递的“声明”。JWT中的声明被编码为JSON对象,用作JSON Web签名(JWS)结构的有效内容或JSON Web加密(JWE)结构的明文,使得声明能够被:数字签名、或利用消息认证码(MAC)保护完整性、加密。

    JWT的声明(Claims)就是一小段信息,用“键-值”对表示。

    想要详细了解<a href="https://tools.ietf.org/html/rfc7515" target="_blank">JSON Web签名(JWS)</a>和<a href="https://tools.ietf.org/html/rfc7516" target="_blank">JSON Web加密(JWE)</a>,可以自行去IETF的网站查阅规范,下文中我会简单的介绍它们。

    JWT的构成

    JWT由三部分组成:

    • Header:头部,即JOSE Header
    • Claims:声明,即JWS Paylaod
    • Signature:签名,即JWS Signature

    JWT由这三部分组成,每一部分都是使用base64url编码的,并使用句点(.)连接起来。这里使用base64url编码而不是普通的base64,是因为base64编码会产生+/,这两个字符在URL中是有特殊意义的,会导致JWT不是URL安全的

    下面以<a href="http://jwt.io" target="_blank">JWT.io</a>首页的一个例子介绍JWT的组成。再用Golang通过这些JSON对象生成JWT,最后用<a href="http://github.com/dgrijalva/jwt-go" target="_blank">jwt-go</a>包比对生成的JWT。

    JWT标准并没有规定必须清除JSON结构中开头结尾的空白符和换行,但是为了消除歧义,一般在使用JSON对象时不用换行,并去掉多余的空白符,这会在我们的代码中有所体现。

    为了方便查看,下面展示代码时使用的都是格式化后的JSON对象。

    头部(JOSE Header)

    JSOEJSON Object Signing and Encryption,即JSON对象签名与加密的缩写。

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

    示例中给出了两个声明:

    • typ: (Type)类型。在JOSE Header中这是个可选参数,但这里我们需要指明类型是JWT
    • alg: (Algorithm)算法,必须是JWS支持的算法,算法列表可以在<a href="https://tools.ietf.org/html/rfc7518" target="_blank">JSON Web算法(JWA)</a>。这里指定算法为HS256

    例子中只列举了两个声明,更多的声明和其具体定义可以到<a href="https://tools.ietf.org/html/rfc7515" target="_blank">JSON Web签名(JWS)</a>中查看。

    Golang代码:

    ...
    header := []byte(`{
      "typ": "JWT",
      "alg": "HS256"
    }`)
    
    buffer := new(bytes.Buffer)
    //去掉多余的换行和空白符
    json.Compact(buffer, header)
    //Base64URL编码
    jwtHeader := base64.URLEncoding.EncodeToString(buffer.Bytes())
    //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    fmt.Println(jwtHeader)
    ...
    

    上述代码片段会输出eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,这就是编码后的JWT头部。

    声明(JWT Claims)

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

    例子中给的是一个注册的声明(sub),和两个私有的声明(nameadmin)。

    注册的、公开的、私有的

    在一个声明集当中,一般会有如下注册的声明名字

    • iss: (Issuer)签发者
    • iat: (Issued At)签发时间,用Unix时间戳表示
    • exp: (Expiration Time)过期时间,用Unix时间戳表示
    • aud: (Audience)接收该JWT的一方
    • sub: (Subject)该JWT的主题
    • nbf: (Not Before)不要早于这个时间
    • jti: (JWT ID)用于标识JWT的唯一ID

    上面的声明都是可选的,但是一般都达成共识,
    注册的声明是在IANA中注册的,
    公开的声明要保证不引起命名冲突
    私有的声明可以使用

    Golang代码:

    ...
    claims := []byte(`{
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true
    }`)
    
    buffer := new(bytes.Buffer)
    json.Compact(buffer, claims)
    jwtClaims := base64.URLEncoding.EncodeToString(buffer.Bytes())
    fmt.Println(jwtClaims)
    ...
    

    上述代码片段会输出eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9,这就是编码后的JWT声明。

    签名(Signature)

    按照头部中指定的,我们要使用HS256算法对上面的编码后的字符串进行签名。
    头部和声明用.号连接起来:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    

    我们要做的就是对这个字符串进行签名。

    Golang代码:

    ...
    //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    s := strings.Join([]string{jwtHeader, jwtClaims}, ".")
    //HS256算法,key是"secret"
    mac := hmac.New(sha256.New, []byte("secret"))
    mac.Write([]byte(s))
    expectedMAC := mac.Sum(nil)
    //TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    signature := strings.TrimRight(base64.URLEncoding.EncodeToString(expectedMAC), "=")
    fmt.Println(signature)
    ...
    

    上述代码输出TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ,这就是这个JWT的签名。

    将头部、声明、签名用.号连在一起就得到了我们要的JWT。

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    验证

    ...
    //定义
    type MyCustomClaims struct {
        Sub   string `json:"sub"`
        Name  string `json:"name"`
        Admin bool   `json:"admin"`
    }
    //实现Claims接口
    func (m MyCustomClaims) Valid() error {
        return nil
    }
    
    mySigningKey := []byte("secret")
    
    claims2 := MyCustomClaims{
        "1234567890",
        "John Doe",
        true,
    }
    
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims2)
    ss, err := token.SignedString(mySigningKey)
    fmt.Printf("%v %v\n", ss, err)
    if ss == s {
        fmt.Println("OK")
    }
    ...
    
    // Encode JWT specific base64url encoding with padding stripped
    func EncodeSegment(seg []byte) string {
        return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
    }
    
    // Decode JWT specific base64url encoding with padding stripped
    func DecodeSegment(seg string) ([]byte, error) {
        if l := len(seg) % 4; l > 0 {
            seg += strings.Repeat("=", 4-l)
        }
        return base64.URLEncoding.DecodeString(seg)
    }
    

    不安全的JWT

    签名为空的JWT

    创建JWT

    按一下步骤创建:

    对UTF-8的八进制序列进行Base64url编码

    一些可以应用JWT的案例

    注意:下面的例子设计并不完善,甚至存在漏洞。这里仅仅是展示JWT的用途。不要将例子直接用于生产环境。

    验证用户

    签发JWT

    1.客户端发送带有用户名、密码的表单到服务器;
    2.服务器验证用户名密码后,将user_id作为JWT Claims中的一个声明,生成JWT;
    3.将签发的JWT作为cookies的内容发送给用户。

    这里要注意,JWT作为cookies的一部分,本质上还是cookies,所以还是要遵循一般的安全原则,防止XSS等攻击手段。

    验证请求

    1.客户端发送带有JWT的请求到服务器;
    2.服务器从JWT中提取信息;
    3.验证JWT是否合法(签名是否正确、令牌是否过期、请求时间在nbf之前还是之后、签发人是否被接受、服务器是否是真正的接受者等);
    4.从声明中取出user_id

    和session的区别

    session需要在服务器中存储标记用户的信息,比如session_id,而JWT则需要。

    JWT在服务器端需要一定量的计算,而session方式一般不需要。

    在分布式系统中,使用Session的方式,需要在多台服务器之间session id,增加了服务器的内存和IO压力。而JWT方式则免去了同步的麻烦。因为用户的状态已经存储在客户端中了,虽然增加了一些计算开销,但是与IO开销比起来,还是要好很多的。

    单点登录

    Set-Cookie: jwt=header.claims.signature; HttpOnly; max-age=980000; domain=.yourdomain.com
    

    我们将域名设置为顶级域名(域名前要加.),这样yourdomain.com*.yourdomain.com都能接收这个cookies了。

    免登陆退订订阅邮件功能

    我们的邮箱中经常会收到一些订阅邮件,有一些

    一些有用的链接

    <a href="http://jwt.io" target="_blank">JWT.io</a>
    <a href="https://www.sitepoint.com/using-json-web-tokens-node-js/" target="_blank">Using JSON Web Tokens with Node.js</a>

    相关文章

      网友评论

          本文标题:jwt教程

          本文链接:https://www.haomeiwen.com/subject/asdzyxtx.html