基于GO的即时聊天后端项目(五)鉴权与加密

本文介绍了如何在Gin项目中集成JWT进行用户授权和鉴权,包括JWT的简要概念、如何生成和验证JWTtoken,以及如何结合MD5加密和盐值处理用户密码。重点展示了在Service层API中如何通过JWT实现登录权限控制和密码安全存储。
摘要由CSDN通过智能技术生成

在第四节 Gin集成与Service层用户API开发中,我们实现了一些用户模块的Service层API。但并不是所有人都可以调用这些API,而是需要有一定的权限才能使用User API。具体在我们的项目中,该权限就是用户是否登录,也就是说,未登录的用户并不能接触到除了登录注册外的任何API;而已登录用户就可以使用所有的API。
在本项目中,我们使用JWT授权与鉴权来实现登录权限的授予与检验。

JWT的简要介绍

  • JWT是一种以json格式颁发的Web服务令牌,持有该令牌就可以获得一定权限,访问一些被保护的资源(如登录状态与游客状态)
  • JWT授权、鉴权在中间件(middleware)中进行
  • 对于加密算法而言,加密方式被分为:
    • 对称加密(授权与鉴权使用同一份秘钥)
    • 非对称加密(私钥生成token,公钥进行验证)
  • 而JWT使用的加密算法是一种非对称加密

项目集成JWT

1. 拉取依赖

go get github.com/dgrijalva/jwt-go

2. 授权

授权需要生成一个特定于用户的token,并携带在服务器的返回信息中告知用户,往后用户的所有API请求都应该添加上token参数。我们在 middleware/jwt.go 下实现 token 的生成代码:

// Claims carry some information about users, such as user_id, expiredTime and issuer
type Claim struct {
	UserId uint `json:"user_id"`
	jwt.StandardClaims
}

// GenerateToken return a token generate by the user and issuer information
func GenerateToken(userId uint, issuer string) (string, error) {
	// Set effective time of token
	curTime := time.Now()
	expiredTime := curTime.Add(5 * time.Minute)

	// Set token information
	claim := Claim{
		UserId: userId,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expiredTime.Unix(),
			Issuer:    issuer,
		},
	}

	// Get string token
	tokenClaim := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
	token, err := tokenClaim.SignedString(JwtSecret)
	return token, err
}

在 /service/user.go 的用户登录API UserLoginByNameAndPwd 中需要添加生成token并传递给用户的代码:

// JWT identify
	token, err := middleware.GenerateToken(Rsp.ID, "xy")
	if err != nil {
		zap.S().Info("Failed to generate token")
		common.SendErrorResp(ctx.Writer, http.StatusInternalServerError, "Failed to generate token", nil)
		return
	}

	data := make(map[string]string)
	data["token"] = token
	data["userId"] = strconv.Itoa(int(Rsp.ID))
	common.SendNormalResp(ctx.Writer, "Success to Login in", data, user, 1)

3. 鉴权

鉴权的核心逻辑是从token中解析出Claim结构体以提取出用户信息

// ParseToken get the claim information from input token
func ParseToken(token string) (*Claim, error) {
	tokenClaim, err := jwt.ParseWithClaims(token, &Claim{}, func(token *jwt.Token) (interface{}, error) {
		return JwtSecret, nil
	})

	if tokenClaim != nil {
		// exchange type: *Token -> *Claim
		if claim, ok := tokenClaim.Claims.(*Claim); ok && tokenClaim.Valid {
			return claim, nil
		}
	}
	return nil, err
}

鉴权的流程:

  1. 从请求行参数中获取token与id
  2. 将id转为uint类型,判断id是否合法
  3. 然后判断token是否为空,不为空则尝试解析token
  4. 最后判断token是否过期

注意:在鉴权函数中必须调用 ctx.next() 以将控制权交给下一个中间件或者handler函数,否则会一直停在该中间件中

// Authentication used in middleware to check if token validate
func Authentication() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		token := ctx.Request.PostFormValue("token")
		id := ctx.Request.PostFormValue("id")

		userId, err := strconv.Atoi(id)
		if err != nil {
			zap.S().Info("Illegal UserId")
			ctx.JSON(http.StatusUnauthorized, gin.H{
				"msg": "Illegal UserId",
			})
			ctx.Abort()
			return
		}

		if token == "" {
			zap.S().Info("Not Login in yet")
			ctx.JSON(http.StatusUnauthorized, gin.H{
				"msg": "Please Login in",
			})
			ctx.Abort()
			return
		} else {
			claim, err := ParseToken(token)
			if err != nil {
				zap.S().Info("token invalidity")
				ctx.JSON(http.StatusUnauthorized, gin.H{
					"msg": "token invalidity, Please Login in again",
				})
				ctx.Abort()
				return
			} else if claim.ExpiresAt < time.Now().Unix() {
				zap.S().Info("token expired")
				ctx.JSON(http.StatusUnauthorized, gin.H{
					"msg": "token expired, Please Login in again",
				})
				ctx.Abort()
				return
			}

			if claim.UserId != uint(userId) {
				zap.S().Info("Illegal Login in")
				ctx.JSON(http.StatusUnauthorized, gin.H{
					"msg": "Login Illegal",
				})
				ctx.Abort()
				return
			}

			fmt.Print("Success to Login in")
			ctx.Next()
		}

	}
}
  1. 在路由中添加鉴权步骤
  • 注意在登录API与注册API不应该添加鉴权
user.GET("/list", middleware.Authentication(), service.UserList)
user.POST("/login", service.UserLoginByNameAndPwd)
user.POST("/new", service.UserRegister)
user.POST("/update", middleware.Authentication(), service.UpdateUserInformation)
user.DELETE("/delete", middleware.Authentication(), service.DeleteUser)

注意在Gin框架中,鉴权函数更推荐以下格式:

func func_name() gin.HandlerFunc{
  return func(ctx *gin.Context) {...}
}

MD5加密

由于用户的登录密码并不建议使用明文存储在数据库中,因此需要对密码进行加密,并存储在数据库中。在本项目中,我们使用MD5算法与加盐算法对用户密码进行加密操作。具体信息可以查阅CSDN文章:加密 / MD5算法 /盐值

盐值

在用户注册时,服务器会自动生成一个随机的字符串作为该用户的盐值

// Generate Salt value
	salt := fmt.Sprintf("%d", rand.Int31())
	user.Salt = salt

Md5加密函数

在 common/password_encoder.go 下实现md5加密函数:

import (
	"crypto/md5"
	"encoding/hex"
	"io"
)

// Md5Encoder Encode the PassWord by Md5
func Md5Encoder(code string) string {
	m := md5.New()
	io.WriteString(m, code)
	return hex.EncodeToString(m.Sum(nil))
}

加盐密码生成和验证

同样在 common/password_encoder.go 下添加加盐密码生成和验证函数:

// CheckPassword Encode the input password and compare the password in DB
func CheckPassword(curPwd string, salt string, dbPwd string) bool {
	pwd := SaltPassword(curPwd, salt)
	return pwd == dbPwd
}

// SaltPassword return the password with salt
func SaltPassword(pwd string, salt string) string {
	saltPwd := fmt.Sprintf("%s%s", Md5Encoder(pwd), salt)
	return saltPwd
}
  • 42
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值