Go-web开发快速入门——二、使用jwt配合中间件进行用户认证(附源码,超详细)

Go-web开发快速入门——二、使用jwt配合中间件进行用户认证(附源码,超详细),第1张

在上一篇博客,我们完成了用户注册登录的功能以及数据库存储用户信息,这篇博客,我们将介绍如何使用jwt配合中间件进行用户认证

上一篇博客链接:Go-web开发快速入门——一、gin+gorm完成用户注册登录及用数据库存储用户信息

参考资料:

生成解析token · Go语言中文文档 (topgoer.com)

一、准备

首先我们将jwt包引入进来,在终端输入代码:

go get github.com/dgrijalva/jwt-go

这里介绍以下jwt token的组成:

jwt web token由三部分组成,用.隔开,三部分为

Header 头部:表明签名所使用的加密算法PayLoad有效载荷:用于储存信息,应包含claimsSignature签名:通过Header申请的算法加密Header和Payload JSON数据 二、编写Go程序

在common包中新建jwt.go,首先定义我们的加密密钥和claim(claim可以使用标准字段,也可使用自定义字段)

//jwt加密密钥
var jwtKey = []byte("a_secret_crect")

//token的claim
type Claims struct {
	UserId uint
	jwt.StandardClaims
}

然后定义发放token的函数

//发放token
func ReleaseToken(user model.User) (string, error) {

	//token的有效期
	expirationTime := time.Now().Add(7 * 24 *time.Hour)

	claims := &Claims{

		//自定义字段
		UserId: user.ID,
		//标准字段
		StandardClaims: jwt.StandardClaims{

			//过期时间
			ExpiresAt: expirationTime.Unix(),
			//发放的时间
			IssuedAt: time.Now().Unix(),
			//发放者
			Issuer: "127.0.0.1",
			//主题
			Subject: "user token",
		},
	}

	//使用jwt密钥生成token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtKey)

	if err != nil {
		return "", err
	}

	//返回token
	return tokenString, nil
}

有生成token的函数相应的就有解析token的函数,这个函数可以把claim的内容解析出来,方便我们使用

	//使用jwt密钥生成token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtKey)

	if err != nil {
		return "", err
	}

	//返回token
	return tokenString, nil
}

//从tokenString中解析出claims并返回
func ParseToken(tokenString string) (*jwt.Token, *Claims, error){
	claims := &Claims{}

	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
		return jwtKey, nil
	})
	return token, claims, err
}

整个common/jwt.go的完整代码如下:

package common

import (
	"demo/model"
	"time"
	"github.com/dgrijalva/jwt-go"
)

//jwt加密密钥
var jwtKey = []byte("a_secret_crect")

//token的claim
type Claims struct {
	UserId uint
	jwt.StandardClaims
}

//发放token
func ReleaseToken(user model.User) (string, error) {

	//token的有效期
	expirationTime := time.Now().Add(7 * 24 *time.Hour)

	claims := &Claims{

		//自定义字段
		UserId: user.ID,
		//标准字段
		StandardClaims: jwt.StandardClaims{

			//过期时间
			ExpiresAt: expirationTime.Unix(),
			//发放的时间
			IssuedAt: time.Now().Unix(),
			//发放者
			Issuer: "127.0.0.1",
			//主题
			Subject: "user token",
		},
	}

	//使用jwt密钥生成token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtKey)

	if err != nil {
		return "", err
	}

	//返回token
	return tokenString, nil
}

//从tokenString中解析出claims并返回
func ParseToken(tokenString string) (*jwt.Token, *Claims, error){
	claims := &Claims{}

	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
		return jwtKey, nil
	})
	return token, claims, err
}

然后编写我们的中间件,新建一个middleware包,在里面新建一个AuthMiddleware.go,该中间件用于判断token是否有效,并将有效的token解析,这里由于我们把用户的id作为其中的一个字段,因此可以将用户的id提取出来,在数据库中查找该用户

middleware/AuthMiddleware.go的完整代码如下:

package middleware

import (
	"demo/common"
	"demo/model"
	"net/http"
	"strings"
	"github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {

	return func(ctx *gin.Context) {

		// 获取authorization header
		tokenString := ctx.GetHeader("Authorization")

		// validate token formate
		if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") {
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "权限不足"})
			ctx.Abort()
			return
		}

		//提取token的有效部分("Bearer "共占7位)
		tokenString = tokenString[7:]

		token, claims, err := common.ParseToken(tokenString)
		if err != nil || !token.Valid {
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "权限不足"})
			ctx.Abort()
			return
		}

		// 验证通过后获取claim 中的userId
		userId := claims.UserId
		DB := common.GetDB()
		var user model.User
		DB.First(&user, userId)

		// 用户不存在
		if user.ID == 0 {
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "权限不足"})
			ctx.Abort()
			return
		}

		// 用户存在将user的信息写入上下文,方便读取
		ctx.Set("user", user)

		ctx.Next()
	}
}

我们拟定在用户登录成功后发放token,并且用这个token可以获取用户信息

修改controller/userControl.go中的login函数并写入Info函数,其内容如下:

//登录
func Login(ctx *gin.Context) {

	db := common.GetDB()

	//获取参数
	//此处使用Bind()函数,可以处理不同格式的前端数据
	var requestUser model.User
	ctx.Bind(&requestUser)
	telephone := requestUser.Telephone
	password := requestUser.Password
	
	//数据验证
	if len(telephone) != 11 {
		ctx.JSON(http.StatusUnprocessableEntity, gin.H{
			"code":    422,
			"message": "手机号必须为11位",
		})
		return
	}
	if len(password) < 6 {
		ctx.JSON(http.StatusUnprocessableEntity, gin.H{
			"code":    422,
			"message": "密码不能少于6位",
		})
		return
	}
	
	//判断手机号是否存在
	var user model.User
	db.Where("telephone = ?", telephone).First(&user)
	if user.ID == 0 {
		ctx.JSON(http.StatusUnprocessableEntity, gin.H{
			"code":    422,
			"message": "用户不存在",
		})
		return
	}
	
	//判断密码是否正确
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
		ctx.JSON(http.StatusUnprocessableEntity, gin.H{
			"code":    422,
			"message": "密码错误",
		})
	}

	//发放token
	token, err := common.ReleaseToken(user)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{
			"code": 	500,
			"message": "系统异常",
		})
		//记录下错误
		log.Printf("token generate error: %v", err)
		return
	}
	
	//返回结果
	ctx.JSON(http.StatusOK, gin.H{
		"code":    200,
		"data": 	gin.H{"token": token},
		"message": "登录成功",
	})
}

func Info(ctx *gin.Context) {

	user, _ := ctx.Get("user")
	//将用户信息返回
	ctx.JSON(http.StatusOK, gin.H{
		"data": gin.H{"user": user},
	})

}

修改routes.go,用中间件保护controller.info,保证token正确且有效时才返回用户信息

package main

import (
	"demo/controller"
	"demo/middleware"
	"github.com/gin-gonic/gin"
)

func CollectRoutes(r *gin.Engine) *gin.Engine {

	//注册
	r.POST("/register", controller.Register)
	//登录
	r.POST("/login", controller.Login)
	//返回用户信息
	r.GET("/info", middleware.AuthMiddleware(), controller.Info)

	return r

}
三、测试

用postman测试,登录成功后可以看到token

我们解析下token可以看到各部分的内容

JWT Token在线解析解码 - ToolTT在线工具箱

在127.0.0.9090/userinfo输入正确的token,将返回用户信息

若token错误,则不会返回用户信息(中间件的作用体现在这里)

四、优化

可以看到,我们输入正确的token后,会出现整个token的解析内容,但显然我们并不需要那么多,于是我们可以定义一下结果返回的样式

新建一个dto包,新建userDto.go,写入代码:

package dto

import "demo/model"

type UserDto struct {
	Name      string `json:"name"`
	Telephone string `json:"telephone"`
}

//只返回name和手机号
func TouserDto(user model.User) UserDto {
	return UserDto{
		Name:      user.Name,
		Telephone: user.Telephone,
	}
}

修改controller/userControl.go中的Info函数

func Info(c *gin.Context) {

	user, _ := c.Get("user")
	//将用户信息返回
	c.JSON(http.StatusOK, gin.H{
		"data": gin.H{"user": dto.TouserDto(user.(model.User))},
	})

}

测试一下,结果只返回了我们所定义的内容

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/990484.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存