在Go IRIS中使用JWT

文章目录

  • 在Go IRIS中使用JWT
    • 1. 跨域认证
    • 2. 关于JWT
    • 3. JWT 工作原理
    • 4. 在Iris中使用 JWT

在Go IRIS中使用JWT

1. 跨域认证

互联网服务离不开用户认证,一种认证方式是:

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

2. 关于JWT

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

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "name":    name, // 用户名
  "exp":      time.Now().Add(time.Hour * 2).Unix(), // 添加过期时间
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

实际的 JWT 大概就像下面这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTk4MDMyODEsInBhc3N3b3JkIjoiMTIzMyIsInBob25lIjoiMTU5MDIwMTUwNDMifQ.NvKsiKh37pxmRH4inKb32EXT-XkSfIC96nEX7p1RLag

它是一个很长的字符串,中间用点(.)分隔成三个部分:Header.Payload.Signature

  • Header(头部):一个 JSON 对象,描述 JWT 的元数据,一般如下所示:
{
  "alg": "HS256",
  "typ": "JWT"
}

alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。

  • Payload(负载):也是一个 JSON 对象,用来存放实际需要传递的数据

Payload示例

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

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

  • Signature(签名):对前两部分的签名,防止数据篡改;首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

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

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
`

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

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

3. JWT 工作原理

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

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

Authorization: Bearer <token>

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

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

使用过程:

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

4. 在Iris中使用 JWT

在go中,开发者可以使用"github.com/dgrijalva/jwt-go" 很简单地使用JWT功能:

首先,终端输入下面的命令下载相关的包:

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

之后,我们就可以直接import使用jwt包了:

import (
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/kataras/iris"
)

在之前的博客:Go iris 入门,里面提到了中间件的概念,JWT验证的方式也是一样的,我们可以自定义一个JWT 中间件来进行JWT验证:

下面的代码是一个自定义JWT中间件的简单例子,里面使用的密钥是My Secret,(因此在加密的时候需要使用同样的密钥),加密算法是HS256

jwtHandler := jwtmiddleware.New(jwtmiddleware.Config{
    //这个方法将验证jwt的token
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
        //自己加密的秘钥或者说盐值
        return []byte("My Secret"), nil
    },
    //设置后,中间件会验证令牌是否使用特定的签名算法进行签名
    //如果签名方法不是常量,则可以使用ValidationKeyGetter回调来实现其他检查
    //重要的是要避免此处的安全问题:https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
    //加密的方式
    SigningMethod: jwt.SigningMethodHS256,
    //验证未通过错误处理方式
    //ErrorHandler: func(context.Context, string)

    //debug 模式
    //Debug: bool
})

解释:
jwtmiddleware.New是配置中间件的错误返回,是否为调试模式,机密秘钥,加密模式等
app.Use(jwtHandler.Serve) 是把中间件注册到处理程序中
注册次中间件的路由,中间件每一次都会去获取header头Authorization字段用户判断

生成加密串过程

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "nick_name": "iris",
    "email":"[email protected]",
    "id":"1",
    "iss":"Iris",
    "iat":time.Now().Unix(),
    "jti":"9527",
    "exp":time.Now().Add(10*time.Hour * time.Duration(1)).Unix(),
})

把token已约定的加密方式和加密秘钥加密,当然也可以使用不对称加密

tokenString, _ := token.SignedString([]byte("My Secret"))

登录时候,把tokenString返回给客户端,然后需要登录的页面就在header上面附此字符串

eg: header["Authorization"] = "bears "+tokenString

在需要验证的地方,可以插入该中间件,关于中间件的使用可以参考上一篇论文:

user := app.Party("/user")
user.Post("/register", userRegisterHandler)
user.Post("/login", userLoginHandler)
// 这里的/profile路由的返回结果是用户的个人信息,因此可以知道,token的验证必要的,因此,我们使用jwtHandler.Serve进行token的验证
user.Get("/profile", jwtHandler.Serve, userTokenHandler, userProfileHandler)

上面提到了验证的方法,那么服务端如何生成token呢?

下面是一个服务端生成token返回给用户的简单例子:

可以知道,JWT token的生成是在登录的时候发生的,因此下面是userLoginHandler的代码:

func userLoginHandler(ctx iris.Context) {
	phone := ctx.FormValue("phone")
	password := ctx.FormValue("password")

	/* 这里省去了对用户的验证,在实际使用过程中需要验证用户是否存在,密码是否正确 */
    
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"phone":    phone,
		"password": password,
		"exp":      time.Now().Add(time.Hour * 2).Unix(), // 添加过期时间为2个小时
	})
 	
    // 这里的密钥和前面的必须一样
	tokenString, _ := token.SignedString([]byte("My Secret"))
	tokenResponse := helper.Token_Response{
		Code: 200,
		Msg:  "登录成功!",
		Data: map[string]string{"token": tokenString}}
    
	ctx.JSON(tokenResponse)
}

你可能感兴趣的:(学习笔记)