go-jwt学习总结

一.概念

jwt,全名(json web token),是一种跨域的认证的解决方案,属于一个开放的标准。使用其规定了一种token的实现方式。

二.为什么使用

传统的的web项目,使用的都是session来认证用户的信息,具体的流程如下:

1.用户通过浏览器将账号跟密码传输给后台服务。

2.服务端对用户跟密码校验后会生成一份保存当前用户信息的session和一个对应的session_id。

3.如果返回响应的时候会将生成的session_id一并返回,浏览器会将该值写入cookie中。

4.如果用户再次通过该浏览器访问服务时,都会携带含有session_id的cookie信息至服务端。

5.服务端接收到请求后,利用cookie中session_id查找对应的账号相关的session信息,从而再次验证当前请求。

6.对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效,前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次。cookie无法跨域,不适用于单点登录。
 

7.过程图;

go-jwt学习总结

存在的缺陷:

1.极度依赖浏览器保存cookie机制。并且再服务端,需要为每一个账号都创建一个session来保存相关信息。(存储消耗较大,如果用户量大,分布式存储也会持续的增大并且维护力度增加)。

2.用户登录方式多样化(不可能总是同一个浏览器,限制了*度),例如用户使用第三方或者app来访问。如果再次使用cookie模式,就会很是有1中的问题,并且也不灵活。

三,jwt的使用

1.过程图:

go-jwt学习总结

2.结构:

  jwt-token由三部分组成,header + payload + signature组成。公式如下:

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret) 

header部分:

是一个json对象:

{

  "typ": "JWT",

   "alg": 加密方法,例如(HS256)

}

payload:

有效载荷部分,jwt主题部分,

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

除此之外,可以可以根据需要自定义相关的信息(不要填充保密信息,因为该部分在加密时,仅仅用base64直接加密,可以直接反编码出来),如下:

{
 "user_id": 0
 "username":"",
}

 例如,一段使用go填充的结构:

cla := MyClaims{
		username,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    "lx-jwt",                                   // 签发人
		},
	}

type StandardClaims struct {
	Audience  string `json:"aud,omitempty"`
	ExpiresAt int64  `json:"exp,omitempty"`
	Id        string `json:"jti,omitempty"`
	IssuedAt  int64  `json:"iat,omitempty"`
	Issuer    string `json:"iss,omitempty"`
	NotBefore int64  `json:"nbf,omitempty"`
	Subject   string `json:"sub,omitempty"`
}

Signature:

签名是对上面两部分的数据进行签名,过程是使用base64编码后的数据,通过制定的算哈生成哈希(不可逆),这样用于确保数据不会被篡改。另外,还需要一个密钥secret,该secret仅仅保存在服务端,并且不可公开。公式如下:

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

最终生成的token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyTmFtZSI6Imx4IiwiZXhwIjoxNjQyODI3ODgxLCJpc3MiOiJseC1qd3QifQ.gWuzFlSSBNC8bzR-txHkHpTtOUd9ELNFczdAiwXicVY

用户每次登陆后,通过从服务端获取对应的token,从而通过认证,在具体请求中携带服务端授予的token,服务端,通过对token的解析验证,确保本次起请求的正确。

3.使用go的go-jwt实现的一个模拟获取token以及请求认证过程:

jwt_use.go

package jwt_use

import (
	"errors"
	"fmt"
	"time"

	"github.com/dgrijalva/jwt-go"
)

const TokenExpireDuration = time.Hour * 2
// const TokenExpireDuration = time.Second * 60

var Secret = []byte("人生路漫漫")

type MyClaims struct {
	UserName string
	jwt.StandardClaims
}

// get token
func GetToken(username string) (string, error) {
	cla := MyClaims{
		username,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    "lx-jwt",                                   // 签发人
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, cla)
	fmt.Println("Token = ",token)
	return token.SignedString(Secret) // 进行签名生成对应的token
}

// parse token
func ParseToken(tokenString string) (*MyClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		return Secret, nil
	})
	if err != nil {
		return nil, err
	}
	if claims,ok := token.Claims.(*MyClaims);ok && token.Valid {
		return claims,nil
	}
	return nil,errors.New("invalid token")
}

user.go

package model

type UserInfo struct {
	UserName string `json:"user_name" form:"user_name"`
	PassWord string `json:"pass_word" form:"pass_word"`
}

main.go

package main

import (
	"encoding/base64"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"git.code.oa.com/my-jwt/jwt_use"
	"git.code.oa.com/my-jwt/model"
	"github.com/gin-gonic/gin"
	"github.com/unrolled/secure"
)

// 通过鉴权账户,并生成对应的token进行返回
func authHandler(c *gin.Context) {

	// tokenString, _ := jwt_use.GetToken("lx")
	// c.JSON(200, gin.H{"code": 0, "msg": "success", "data": gin.H{"token": tokenString}})
	// return

	user := &model.UserInfo{}
	err := c.ShouldBindJSON(user)
	if err != nil {
		c.JSON(200, gin.H{"code": 2001, "msg": "invalid params"})
		return
	}

	// 检查人员是否存在,并为其生成一个token,单点登陆友好选择jwt
	if user.UserName == "lx" && user.PassWord == "123qweasd" {
		tokenString, _ := jwt_use.GetToken(user.UserName)
		c.JSON(200, gin.H{"code": 0, "msg": "success", "data": gin.H{"token": tokenString}})
		return
	}
	c.JSON(200, gin.H{"code": 2002, "msg": "鉴权失败"})
	return
}

// 中间件,认证token合法性
func jwtAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHandler := c.Request.Header.Get("authorization")
		if authHandler == "" {
			c.JSON(200, gin.H{"code": 2003, "msg": "请求头部auth为空"})
			c.Abort()
			return
		}

		// 前两部门可以直接解析出来
		jwt := strings.Split(authHandler, ".")
		cnt := 0
		for _, val := range jwt {
			cnt++
			if cnt == 3 {
				break
			}
			msg, _ := base64.StdEncoding.DecodeString(val)
			fmt.Println("val ->", string(msg))
		}

		// 我们使用之前定义好的解析JWT的函数来解析它,并且在内部解析时判断了token是否过期
		mc, err := jwt_use.ParseToken(authHandler)
		if err != nil {
			fmt.Println("err = ", err.Error())
			c.JSON(http.StatusOK, gin.H{
				"code": 2005,
				"msg":  "无效的Token",
			})
			c.Abort()
			return
		}

		// 将当前请求的username信息保存到请求的上下文c上
		c.Set("username", mc.UserName)
		c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
	}
}

// 
func homeHandler(c *gin.Context) {
	username := c.MustGet("username").(string)
	c.JSON(http.StatusOK, gin.H{
		"code": 2000,
		"msg":  "success",
		"data": gin.H{"username": username},
	})
}

// ssh加密验证
func TSSHandler(port int) gin.HandlerFunc {
	return func(c *gin.Context) {

		fmt.Println("start ->")
		secureMiddleware := secure.New(secure.Options{
			SSLRedirect: true,
			SSLHost:     ":" + strconv.Itoa(port),
		})
		err := secureMiddleware.Process(c.Writer, c.Request)
		if err != nil {
			fmt.Println("err = ", err.Error())
			return
		}
		fmt.Println("+ err = ", err)
		c.Next()
		fmt.Println("success")
	}
}
func main() {
	r := gin.Default()
	// r.Use(TSSHandler(8080))
	r.POST("/auth", authHandler)
	r.GET("/home", jwtAuthMiddleware(), homeHandler)
	fmt.Println("ser is running")
	r.Run(":18243") // http
	// r.RunTLS(":8080","cert.pem", "key.pem") // https
}

上一篇:golang压缩文件为zip【我的第一个golang程序】


下一篇:再谈docker基本命令