JWT

JWT介绍

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对进行签名。

什么场景适合用JWT

  • 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On(单点登录)是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
  • 信息交换JWT是在各方之间安全传输信息的好方法。 因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用Headerpayload,Signature,您还可以验证内容是否未被篡改。

JWT结构

在紧凑的形式中,JWT由三个部分组成,用点(.)分隔,

  • Header
  • Payload
  • Signature

因此,JWT通常如下所(token)

  1. xxxxx.yyyyy.zzzzz (Header.Payload.Signature)
  2. `

Header

Header通常由两部分组成:令牌的类型,即JWT,以及正在使用的散列算法,例如HMAC SHA256RSA。示例如下:

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }
  5. `

然后,这个JSON被编码为Base64Url,形成JWT的第一部分。

Payload

token的第二部分是payload,其中包含claimsclaims是关于实体(通常是用户)和其他数据的声明。claims有三种类型:registered, public, private claims

  • Registered claims:这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行人),exp(过期时间),sub(主题),aud(观众)等。
  • Public claims:这些可以由使用JWT的人随意定义。但是为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或者将其定义为包含防冲突命名空间的URI
  • Private claims:这些声明是为了在同意使用它们的各方之间共享信息而创建的,并且既不是注册声明也不是公开声明。

Payload示例

  1. {
  2. "sub": "1234567890",
  3. "name": "John Doe",
  4. "admin": true
  5. }
  6. `

然后,Payload经过Base64Url编码,形成JSON Web Token的第二部分,数据虽然是不可串改,但是确实透明的

Signature

要创建签名部分,您必须采用base64Url编码headerbase64Url编码的payloadsecretheader中指定的算法,并对其进行签名

例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:

  1. HMACSHA256(
  2. base64UrlEncode(header) + "." +
  3. base64UrlEncode(payload),
  4. secret)
  5. `

签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。

合并jwt三部分

输出是三个由点分隔的Base64-URL字符串,可以在HTMLHTTP环境中轻松传递,与SAML等基于XML的标准相比更加紧凑。

下面显示了一个JWT,它具有HeaderPayload,并使Signature

  1. //encoded
  2. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6
  3. IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  4. //decoded
  5. //Header 部分
  6. {
  7. "alg": "HS256",
  8. "typ": "JWT"
  9. }
  10. //Payload 部分
  11. {
  12. "sub": "1234567890",
  13. "name": "John Doe",
  14. "iat": 1516239022
  15. }
  16. //Signature 部分
  17. HMACSHA256(
  18. base64UrlEncode(header) + "." +
  19. base64UrlEncode(payload),
  20. your-256-bit-secret
  21. )

JWT工作原理

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token。由于Token是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。

每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送JWT,通常在Authorization Header中。Header的内容应如下所示:

  1. Authorization: Bearer <token>

在某些情况下,这可以是无状态授权机制。服务器的受保护路由将在Authorization Header中检查有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。

如果在Authorization Header中发送Token,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie

下图显示了如何获取JWT并用于访问API或资源:JWT工作流程图

  • 应用程序或客户端向授权服务器请求授权。这是通过其中一个不同的授权流程执行的。例如,典型的OpenID Connect兼容Web应用程序将使用授权代码流通过/oauth/authorize端点。
  • 授予授权后,授权服务器会向应用程序返回访问Token
  • 应用程序使用访问Token来访问受保护资源(如API)。

请注意,使用签名TokenToken中包含的所有信息都会向用户或其他方公开,即使他们无法更改。这意味着您不应该在Token中放置秘密信息。

目录结构

主目录jwt

  1. —— main.go

代码示例

main.go

  1. // iris提供了一些基本的中间件,大部分用于学习曲线。
  2. //您可以将任何net/http请求的中间件与iris.FromStd(把net/http转化成iris context形式)包装器一起使用
  3. //适用于Golang新手的JWT net/http视频教程:https://www.youtube.com/watchv=dgJFeqeXVKw
  4. //这个中间件是唯一一个从外部源克隆的中间件:https://github.com/auth0/go-jwt-middleware
  5. //(因为它使用“context”来定义用户,但我们不需要这样,因此简单的iris.FromStd将无法按预期工作。)
  6. package main
  7. // $ go get -u github.com/dgrijalva/jwt-go
  8. // $ go run main.go
  9. import (
  10. "github.com/kataras/iris"
  11. "github.com/dgrijalva/jwt-go"
  12. jwtmiddleware "github.com/iris-contrib/middleware/jwt"
  13. )
  14. func myHandler(ctx iris.Context) {
  15. //如果解密成功,将会进入这里,获取解密了的token
  16. token := ctx.Values().Get("jwt").(*jwt.Token)
  17. //或者这样
  18. //userMsg :=ctx.Values().Get("jwt").(*jwt.Token).Claims.(jwt.MapClaims)
  19. // userMsg["id"].(float64) == 1
  20. // userMsg["nick_name"].(string) == iris
  21. ctx.Writef("This is an authenticated request\n")
  22. ctx.Writef("Claim content:\n")
  23. //可以了解一下token的数据结构
  24. ctx.Writef("%s", token.Signature)
  25. }
  26. func main() {
  27. app := iris.New()
  28. //jwt中间件
  29. jwtHandler := jwtmiddleware.New(jwtmiddleware.Config{
  30. //这个方法将验证jwt的token
  31. ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
  32. //自己加密的秘钥或者说盐值
  33. return []byte("My Secret"), nil
  34. },
  35. //设置后,中间件会验证令牌是否使用特定的签名算法进行签名
  36. //如果签名方法不是常量,则可以使用ValidationKeyGetter回调来实现其他检查
  37. //重要的是要避免此处的安全问题:https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
  38. //加密的方式
  39. SigningMethod: jwt.SigningMethodHS256,
  40. //验证未通过错误处理方式
  41. //ErrorHandler: func(context.Context, string)
  42. //debug 模式
  43. //Debug: bool
  44. })
  45. app.Use(jwtHandler.Serve)
  46. //解释:
  47. //jwtmiddleware.New是配置中间件的错误返回,是否为调试模式,机密秘钥,加密模式等
  48. //app.Use(jwtHandler.Serve) 是把中间件注册到处理程序中
  49. //注册次中间件的路由,中间件每一次都会去获取header头Authorization字段用户判断
  50. // 生成加密串过程
  51. // token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
  52. // "nick_name": "iris",
  53. // "email":"go-iris@qq.com",
  54. // "id":"1",
  55. // "iss":"Iris",
  56. // "iat":time.Now().Unix(),
  57. // "jti":"9527",
  58. // "exp":time.Now().Add(10*time.Hour * time.Duration(1)).Unix(),
  59. // })
  60. // 把token已约定的加密方式和加密秘钥加密,当然也可以使用不对称加密
  61. // tokenString, _ := token.SignedString([]byte("My Secret"))
  62. // 登录时候,把tokenString返回给客户端,然后需要登录的页面就在header上面附此字符串
  63. // eg: header["Authorization"] = "bears "+tokenString
  64. app.Get("/ping", myHandler)
  65. app.Run(iris.Addr("localhost:3001"))
  66. }