在上一篇博客,我们完成了用户注册登录的功能以及数据库存储用户信息,这篇博客,我们将介绍如何使用jwt配合中间件进行用户认证
上一篇博客链接:Go-web开发快速入门——一、gin+gorm完成用户注册登录及用数据库存储用户信息
参考资料:
生成解析token · Go语言中文文档 (topgoer.com)
一、准备首先我们将jwt包引入进来,在终端输入代码:
go get github.com/dgrijalva/jwt-go
这里介绍以下jwt token的组成:
jwt web token由三部分组成,用.隔开,三部分为
- Header 头部:表明签名所使用的加密算法
- PayLoad有效载荷:用于储存信息,应包含claims
- Signature签名:通过Header申请的算法加密Header和Payload JSON数据
在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))},
})
}
测试一下,结果只返回了我们所定义的内容
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)