goframe框架签到系统项目开发(用户认证中间件、实现Refresh-token接口)

文章目录

用户认证中间件

增加常量

go 复制代码
package consts

// CtxKey 上下文 key
type CtxKey string

const (
	JWTAccessTokenSecret  = "You're making my blood run beyond dimensions" // JWT 访问令牌密钥
	JWTRefreshTokenSecret = "You're making my blood run out of emissions"  // JWT 刷新令牌密钥
	JWTTokenExpireDay     = 24                                             // JWT 令牌过期时间(小时)
	JWTRefreshExpireWeek  = 7 * 24                                         // JWT 刷新令牌过期时间(小时)

	CtxKeyUserID CtxKey = "userId" // 用户ID上下文 key
)

在logic下创建中间件

go 复制代码
package middleware

import (
	"backend/internal/consts"
	"backend/internal/model"
	"net/http"
	"strings"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/golang-jwt/jwt/v5"
)

// Auth 认证中间件
func Auth(r *ghttp.Request) {
	// 从请求中获取用户id(根据Access token获取用户id)
	// 从请求头中获取 jwt access token
	// r := g.RequestFromCtx(ctx) // 从 ctx 中获取请求对象

	// 获取请求上下文
	ctx := r.GetCtx()

	// 从请求头中获取 Authorization 字段的值
	authorizationValue := r.GetHeader("Authorization")

	// HTTP 请求头里通常会带:Authorization: Bearer <access_token>
	// 检查 Authorization 头值是否以 Bearer 开头(Bearer 是 OAuth2.0 中用于表示携带 JWT 的标准方式)。
	if len(authorizationValue) == 0 || !strings.HasPrefix(authorizationValue, "Bearer ") {
		r.Response.WriteStatusExit(http.StatusForbidden, "缺少token")
	}

	accessToken := strings.TrimPrefix(authorizationValue, "Bearer ")

	// 解析token获取用户id
	var claim model.JWTClaims

	// 解析 JWT:将 JWT 字符串(即 accessToken)分解为 Header、Payload、Signature
	// 验证签名:通过密钥 consts.JWTAccessTokenSecret 来验证 Signature 是否正确,确保 Token 没有被篡改。
	// 填充 claim:将解析出来的 Payload 内容(例如用户的 UserId、Username)填充到 claim 变量中
	// keyFunc:这个是一个回调函数,用来返回签名验证所需的密钥。jwt.ParseWithClaims 会使用它来验证 JWT 的 Signature 是否有效
	token, err := jwt.ParseWithClaims(accessToken, &claim, func(token *jwt.Token) (any, error) {
		return []byte(consts.JWTAccessTokenSecret), nil
	})

	if err != nil || !token.Valid {
		r.Response.WriteStatusExit(http.StatusForbidden, "无效的token")
	}

	g.Log().Debugf(ctx, "claim: %v", claim)

	// 向请求的上下文中写入用户 id
	r.SetCtxVar(consts.CtxKeyUserID, claim.UserId) // 使用自定义 Key 类型,防止被其他中间件覆盖
	// r.SetCtxVar("userId", "xxx")

	r.Middleware.Next() // 继续执行后续中间件
}

修改controller层的me

go 复制代码
package userinfo

import (
	v1 "backend/api/userinfo/v1"
	"backend/internal/consts"
	"context"
	"strconv"

	"github.com/gogf/gf/v2/frame/g"

	"github.com/gogf/gf/v2/errors/gerror"
)

func (c *ControllerV1) Me(ctx context.Context, req *v1.MeReq) (res *v1.MeRes, err error) {
	// 从请求上下文获取 userId
	userId, ok := ctx.Value(consts.CtxKeyUserID).(uint64)

	g.Log().Debugf(ctx, "从请求上下文中获取 userId: %d", userId)
	if !ok || userId == 0 {
		return nil, gerror.New("用户信息获取失败")
	}

	// 根据用户id获取用户信息
	// FormatUint 用来将 uint64 类型的整数转换为字符串
	userInfo, err := c.svc.GetInfo(ctx, strconv.FormatUint(userId, 10))

	// userInfo, err := c.svc.GetInfo(ctx, "7848643939821819")

	if err != nil {
		return nil, gerror.New("获取用户信息失败")
	}

	// 返回用户信息
	return &v1.MeRes{
		Username: userInfo.Username,
		Avatar:   userInfo.Avatar,
		Email:    userInfo.Email,
	}, err
}

修改cmd文件

go 复制代码
package cmd

import (
	"context"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcmd"

	"backend/internal/controller/hello"
	"backend/internal/controller/userinfo"
	"backend/internal/logic/middleware"
	"backend/utility/injection"
)

var (
	Main = gcmd.Command{
		Name:  "main",
		Usage: "main",
		Brief: "start http server",
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			s := g.Server()

			// 服务注入
			injection.SetupDefaultInjector(ctx)
			defer injection.ShutdownDefaultInjector()

			// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
			s.Group("/api/v1", func(group *ghttp.RouterGroup) {
				group.Middleware(ghttp.MiddlewareHandlerResponse)

				// 不需要登录也能访问的接口
				group.POST("/auth/login", userinfo.NewV1(), "Login") // 登录
				group.POST("/users", userinfo.NewV1(), "Create")     // 创建用户

				// 需要登录才能访问的接口
				group.Middleware(middleware.Auth)
				group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息

				group.Bind(
					hello.NewV1(),
					// userinfo.NewV1(), // 用户模块相关接口
				)
			})
			s.Run()
			return nil
		},
	}
)

启动服务进行测试

复制代码
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzYwMDk0fQ.tJwBpbK1ZJlO7k32bxQZ6sdk1rjg36cuuy0PbKUbuLU

官方给的Context共享变量

设置前后端跨域中间件

go 复制代码
package middleware

import (
	"github.com/gogf/gf/v2/net/ghttp"
)

func CORS(r *ghttp.Request) {
	r.Response.CORSDefault()
	r.Middleware.Next()
}

修改cmd文件注册跨域中间件

go 复制代码
package cmd

import (
	"context"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcmd"

	"backend/internal/controller/hello"
	"backend/internal/controller/userinfo"
	"backend/internal/logic/middleware"
	"backend/utility/injection"
)

var (
	// 运行 main 命令时,程序会进入 gcmd.Command 的 Func 函数,这是你定义的 main 命令的核心部分。
	Main = gcmd.Command{
		Name:  "main",
		Usage: "main",
		Brief: "start http server",
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			// 在 Func 函数内部,首先初始化了一个 GoFrame 服务器
			s := g.Server()

			// 服务注入
			injection.SetupDefaultInjector(ctx)
			defer injection.ShutdownDefaultInjector()

			// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
			s.Group("/api/v1", func(group *ghttp.RouterGroup) {
				// 注册通用响应中间件和CORS跨域中间件
				// ghttp.MiddlewareHandlerResponse 是 GoFrame 的默认响应中间件,负责处理 HTTP 响应的通用逻辑。
				group.Middleware(ghttp.MiddlewareHandlerResponse, middleware.CORS)

				// 不需要登录也能访问的接口
				group.POST("/auth/login", userinfo.NewV1(), "Login") // 登录
				group.POST("/users", userinfo.NewV1(), "Create")     // 创建用户

				// 需要登录才能访问的接口
				group.Middleware(middleware.Auth)
				group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息

				group.Bind(
					hello.NewV1(),
					// userinfo.NewV1(), // 用户模块相关接口
				)
			})
			s.Run()
			return nil
		},
	}
)

实现Refresh-token接口

增加api相关接口定义

go 复制代码
package v1

import (
	"github.com/gogf/gf/v2/frame/g"
)

// CreateReq 创建用户请求结构体
type CreateReq struct {
	g.Meta          `path:"/users" method:"post" tags:"用户模块" sm:"创建用户"`
	Username        string `p:"username" v:"required|length:3,20" dc:"用户名"`
	Email           string `p:"email" v:"required|email" dc:"邮箱"`
	Password        string `p:"password" v:"required|length:6,20" dc:"密码"`
	ConfirmPassword string `p:"confirmPassword" v:"required|same:Password#确认密码必须传|两次密码需保持一致" dc:"确认密码"`
}

// CreateRes 创建用户返回结构体
type CreateRes struct {
	// mime:"application/json" 表示:这个接口的响应类型是 JSON
	g.Meta   `mime:"application/json"`
	UserId   uint64 `json:"userID" dc:"用户ID"`
	Username string `json:"username" dc:"用户名"`
}

type LoginReq struct {
	g.Meta   `path:"/auth/login" method:"post" tags:"用户模块" sm:"登录"`
	Username string `p:"username" v:"required|length:3,20" dc:"用户名"`
	Password string `p:"password" v:"required|length:6,20" dc:"密码"`
}

type LoginRes struct {
	AccessToken  string `json:"accessToken" dc:"访问令牌"`
	RefreshToken string `json:"refreshToken" dc:"刷新令牌"`
}

type MeReq struct {
	// sm即 summary	接口/参数概要描述
	g.Meta `path:"/users/me" method:"get" tags:"用户模块" sm:"获取当前登录的用户信息"`
}

type MeRes struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	Avatar   string `json:"avatar"`
}

type RefreshTokenReq struct {
	g.Meta       `path:"/auth/refresh" method:"post" tags:"用户模块" sm:"刷新令牌"`
	RefreshToken string `p:"refreshToken" v:"required" dc:"刷新令牌"`
}

type RefreshTokenRes struct {
	AccessToken  string `json:"accessToken" dc:"访问令牌"`
	RefreshToken string `json:"refreshToken" dc:"刷新令牌"`
}

生成controller层实现代码

bash 复制代码
root@GoLang:~/proj/proj2/goframProj/backend# gf gen ctrl
generated: /root/proj/proj2/goframProj/backend/api/hello/hello.go
generated: /root/proj/proj2/goframProj/backend/api/userinfo/userinfo.go
generated: /root/proj/proj2/goframProj/backend/internal/controller/userinfo/userinfo_v1_refresh_token.go
done!

定义service层下的业务接口抽象

go 复制代码
package userinfo

import (
	// "backend/internal/dao"
	"backend/internal/model"
	"backend/internal/model/entity"

	// "backend/internal/model/entity"
	"context"
	// "time"
	// "github.com/gogf/gf/crypto/gmd5"
	// "github.com/gogf/gf/v2/errors/gerror"
	// "github.com/gogf/gf/v2/frame/g"
	// "github.com/sony/sonyflake/v2"
)

// 把用户服务抽象成一个接口, 列出来所需要实现的方法
type UserInfoService interface {
	Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error)
	Login(ctx context.Context, input *model.LoginInput) (*model.LoginOutput, error)
	GetInfo(ctx context.Context, userId string) (*entity.Userinfo, error)
	RefreshToken(ctx context.Context, refreshToken string) (res *model.TokenOutput, err error)
}

定义impl下的RefreshToken方法实现

go 复制代码
package impl

import (
	"backend/internal/consts"
	"backend/internal/dao"
	"backend/internal/model"
	"backend/internal/model/entity"
	"backend/internal/service/userinfo"
	"backend/utility/injection"
	"context"
	"strconv"
	"time"

	"github.com/gogf/gf/crypto/gmd5"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/golang-jwt/jwt/v5"
	"github.com/sony/sonyflake/v2"
)

// 用户相关业务逻辑
// 定义一个结构体, 实现UserInfoService接口
type UserInfo struct {
	snowflack *sonyflake.Sonyflake
}

// encryptPassword 加密密码
func (u *UserInfo) encryptPassword(password string) string {
	return gmd5.MustEncryptString(password)
}

// func New() *UserInfo {
// 	return &UserInfo{}
// }

func New() userinfo.UserInfoService {
	return &UserInfo{
		snowflack: injection.MustInvoke[*sonyflake.Sonyflake](),
	}
}

// 建议:在包级别初始化一次(比如 init() 或 main 启动时)
// var sf *sonyflake.Sonyflake

const (
	defaultAvatar = "https://avatars.githubusercontent.com/u/51045272?v=4"
)

// func init() {
// 	st, err := time.Parse(time.DateOnly, "2025-11-01") // 用过去时间
// 	if err != nil {
// 		panic(err) // 这里只在启动阶段 panic 可以接受
// 	}
// 	sf, err = sonyflake.New(sonyflake.Settings{StartTime: st})
// 	if err != nil {
// 		panic(err)
// 	}
// }

// Create 创建用户
// func (u *UserInfo) Create(ctx context.Context, username, password, email string) error {
func (u *UserInfo) Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error) {
	// 1. 判断用户名是否已经存在(根据用户名查重)
	exist, err := dao.Userinfo.Ctx(ctx).
		Where(dao.Userinfo.Columns().Username, input.Username).
		Exist()
	if err != nil {
		g.Log().Errorf(ctx, "查询用户是否存在失败: %v", err)
		return nil, err
	}

	if exist {
		return nil, gerror.New("用户已存在")
	}

	// 2. 生成唯一 id
	userId, err := u.snowflack.NextID()
	if err != nil {
		g.Log().Errorf(ctx, "生成用户ID失败: %v", err)
		return nil, gerror.Wrap(err, "生成用户ID失败")
	}

	// 3. 创建用户
	// 创建用户,入库
	newUserInfo := entity.Userinfo{
		UserId:   uint64(userId), // 使用雪花算法生成唯一ID
		Username: input.Username,
		// Password: req.Password, // 是不是需要对用户输入的密码进行加密
		Password: u.encryptPassword(input.Password), // 对字符串 s 做 MD5, 返回 MD5 的十六进制字符串
		// 练习可用;生产换 bcrypt

		Email:  input.Email,
		Avatar: defaultAvatar, // 简化注册流程,一般使用默认头像,后续支持用户在个人中心上传头像
	}

	// id, err := dao.Userinfo.Ctx(ctx).InsertAndGetId(newUserInfo)

	_, err = dao.Userinfo.Ctx(ctx).Insert(newUserInfo)
	if err != nil {
		g.Log().Errorf(ctx, "创建用户失败:%v", err)
		return nil, gerror.Wrap(err, "创建用户失败")
	}
	// 4. 返回结果
	return &model.CreateUserOutput{
		UserId:   uint64(userId),
		Username: input.Username,
	}, nil
}

// Login 登录
func (u *UserInfo) Login(ctx context.Context, input *model.LoginInput) (*model.LoginOutput, error) {
	// 拿用户输入的用户名和密码,去数据库查询
	var user entity.Userinfo
	err := dao.Userinfo.Ctx(ctx).
		Where(dao.Userinfo.Columns().Username, input.Username).
		Where(dao.Userinfo.Columns().Password, u.encryptPassword(input.Password)).
		Scan(&user)

	if err != nil {
		g.Log().Errorf(ctx, "查询用户失败: %v", err)
		return nil, gerror.Wrapf(err, "查询用户失败")
	}

	// 生成 JWT Token
	tokenObj, err := genJwtByUserInfo(ctx, user.UserId, user.Username)
	if err != nil {
		g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
		return nil, gerror.Wrap(err, "生成 JWT Token 失败")
	}

	// 返回结果
	return &model.LoginOutput{
		AccessToken:  tokenObj.AccessToken,
		RefreshToken: tokenObj.RefreshToken,
	}, nil
}

// type JWTClaims struct {
// 	UserId   uint64 `json:"userId"`
// 	Username string `json:"username"`
// 	jwt.RegisteredClaims
// }

// genJwtByUserInfo 根据用户信息生成 JWT
func genJwtByUserInfo(ctx context.Context, userID uint64, username string) (*model.TokenOutput, error) {
	// 生成 Access Token
	claims := &model.JWTClaims{
		UserId:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "Simon",
			Subject:   "check-in-system",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(consts.JWTTokenExpireDay * time.Hour)), // 设置过期时间为1天
		},
	}

	// 创建一个新的 JWT 对象
	accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// SignedString:把 Header + Payload 按 JWT 规则 base64url 编码后,使用 secret 生成签名,最后拼成标准 JWT 字符串
	// []byte(...):HMAC 需要字节数组形式的 key
	signedAccessToken, err := accessToken.SignedString([]byte(consts.JWTAccessTokenSecret))
	if err != nil {
		g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
		return nil, err
	}

	// 生成 refresh Token
	refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, &model.JWTClaims{
		UserId:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "Simon",
			Subject:   "check-in-system",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(consts.JWTRefreshExpireWeek * time.Hour)), // 设置过期时间为1周
		},
	})

	signedRefreshToken, err := refreshToken.SignedString([]byte(consts.JWTRefreshTokenSecret))
	if err != nil {
		g.Log().Errorf(ctx, "生成 JWT Token 失败: %v", err)
		return nil, err
	}

	return &model.TokenOutput{
		AccessToken:  signedAccessToken,
		RefreshToken: signedRefreshToken,
	}, nil
}

func (u *UserInfo) GetInfo(ctx context.Context, userId string) (*entity.Userinfo, error) {
	var user entity.Userinfo
	err := dao.Userinfo.Ctx(ctx).
		Where(dao.Userinfo.Columns().UserId, userId).
		Scan(&user)

	if err != nil {
		g.Log().Errorf(ctx, "查询用户失败: %v", err)
		return nil, gerror.Wrapf(err, "查询用户失败")
	}
	return &user, nil
}

// RefreshToken 刷新 Token
func (u *UserInfo) RefreshToken(ctx context.Context, refreshToken string) (res *model.TokenOutput, err error) {
	// 1. 解析 refreshToken,拿到 userid
	// 解析token获取用户id
	var claim model.JWTClaims

	// 解析 JWT:将 JWT 字符串(即 accessToken)分解为 Header、Payload、Signature
	// 验证签名:通过密钥 consts.JWTAccessTokenSecret 来验证 Signature 是否正确,确保 Token 没有被篡改。
	// 填充 claim:将解析出来的 Payload 内容(例如用户的 UserId、Username)填充到 claim 变量中
	// keyFunc:这个是一个回调函数,用来返回签名验证所需的密钥。jwt.ParseWithClaims 会使用它来验证 JWT 的 Signature 是否有效
	token, err := jwt.ParseWithClaims(refreshToken, &claim, func(token *jwt.Token) (any, error) {
		return []byte(consts.JWTRefreshTokenSecret), nil
	})

	if err != nil || !token.Valid {
		g.Log().Errorf(ctx, "refresh token: %v, err: %+v", token, err)
		return nil, gerror.New("refresh token 无效")
	}

	// 2. 根据 userid 获取用户信息
	userInfo, err := u.GetInfo(ctx, strconv.FormatUint(claim.UserId, 10))
	if err != nil {
		g.Log().Errorf(ctx, "根据 userid 获取用户信息失败: %v", err)
		return nil, gerror.Wrap(err, "根据 userid 获取用户信息失败")
	}

	// 3. 生成新的 accessToken 和 refreshToken
	// 4. 返回新的 token
	return genJwtByUserInfo(ctx, userInfo.UserId, userInfo.Username)
}

修改control层实现代码

go 复制代码
package userinfo

import (
	"context"

	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"

	v1 "backend/api/userinfo/v1"
)

func (c *ControllerV1) RefreshToken(ctx context.Context, req *v1.RefreshTokenReq) (res *v1.RefreshTokenRes, err error) {
	output, err := c.svc.RefreshToken(ctx, req.RefreshToken)
	if err != nil {
		g.Log().Errorf(ctx, "刷新 token 失败: %v", err)
		return nil, gerror.New("刷新 token 失败")
	}

	return &v1.RefreshTokenRes{
		AccessToken:  output.AccessToken,
		RefreshToken: output.RefreshToken,
	}, nil
}

cmd.go注册刷新token路由

go 复制代码
package cmd

import (
	"context"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcmd"

	"backend/internal/controller/hello"
	"backend/internal/controller/userinfo"
	"backend/internal/logic/middleware"
	"backend/utility/injection"
)

var (
	// 运行 main 命令时,程序会进入 gcmd.Command 的 Func 函数,这是你定义的 main 命令的核心部分。
	Main = gcmd.Command{
		Name:  "main",
		Usage: "main",
		Brief: "start http server",
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			// 在 Func 函数内部,首先初始化了一个 GoFrame 服务器
			s := g.Server()

			// 服务注入
			injection.SetupDefaultInjector(ctx)
			defer injection.ShutdownDefaultInjector()

			// 定义了一个路由组,所有的路由都会以 /api/v1 为前缀。
			s.Group("/api/v1", func(group *ghttp.RouterGroup) {
				// 注册通用响应中间件和CORS跨域中间件
				// ghttp.MiddlewareHandlerResponse 是 GoFrame 的默认响应中间件,负责处理 HTTP 响应的通用逻辑。
				group.Middleware(ghttp.MiddlewareHandlerResponse, middleware.CORS)

				// 不需要登录也能访问的接口
				group.POST("/auth/login", userinfo.NewV1(), "Login")          // 登录
				group.POST("/users", userinfo.NewV1(), "Create")              // 创建用户
				group.POST("/auth/refresh", userinfo.NewV1(), "RefreshToken") // 刷新token

				// 需要登录才能访问的接口
				group.Middleware(middleware.Auth)
				group.GET("/users/me", userinfo.NewV1(), "Me") // 我的信息

				group.Bind(
					hello.NewV1(),
					// userinfo.NewV1(), // 用户模块相关接口
				)
			})
			s.Run()
			return nil
		},
	}
)


注意 group.POST("/auth/refresh", userinfo.NewV1(), "RefreshToken") 的第三个参数是对应的方法名

启动测试

bash 复制代码
{
    "username": "Jackson",
    "password": "1234567"
}
复制代码
{"code":0,"message":"OK","data":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM1OTc5fQ.VVaHmZTh5iigI3tCDeNTn0TYhKqretDTTHtrOfrmzVo","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQwNzU5fQ.72g2gbrCFMwR5K4y_TkqI5UwTP4YmU_0QiiifOEM9mI"}}
复制代码
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM1OTc5fQ.VVaHmZTh5iigI3tCDeNTn0TYhKqretDTTHtrOfrmzVo

20秒后access token会失效


前端会自动拿到refreshtoken来刷新accesstoken

bash 复制代码
{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQwNzU5fQ.72g2gbrCFMwR5K4y_TkqI5UwTP4YmU_0QiiifOEM9mI"
}
bash 复制代码
{"code":0,"message":"OK","data":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM2NDg1fQ.H1XdzyelFdutF_1HXvy0CKCccTQG5Vo8Ku33avx9ndc","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY3MzQxMjY1fQ.NMjYgNKW-4m0Ww04TB7lEB7WxWbx8Aq6z74SRjxhtto"}}

就可以正常登着账户

复制代码
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjc4NDg2NDM5Mzk4MjE4MTksInVzZXJuYW1lIjoiSmFja3NvbiIsImlzcyI6IlNpbW9uIiwic3ViIjoiY2hlY2staW4tc3lzdGVtIiwiZXhwIjoxNzY2NzM2NDg1fQ.H1XdzyelFdutF_1HXvy0CKCccTQG5Vo8Ku33avx9ndc

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

相关推荐
建群新人小猿2 小时前
陀螺匠 企业助手-经我审批
服务器·数据库·php
TDengine (老段)2 小时前
TDengine 小白入门指南
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
码农阿豪2 小时前
从 Oracle 到电科金仓:一次性能优化视角下的深度迁移体验
数据库·oracle·性能优化·金仓数据库
txzz88882 小时前
Linux系统db_load命令
数据库·翻译·db_load命令
2401_876221342 小时前
数据库系统概论(第6版)模拟题1
数据库
cike_y2 小时前
Mybatis之分页的实现&日志工厂&Log4j详解
数据库·log4j·mybatis
YGGP2 小时前
【Golang】LeetCode 300. 最长递增子序列
算法·leetcode
隐语SecretFlow2 小时前
隐语SML0.1.0版本发布!SPU开源机器学习Python算法库
python·算法·机器学习
zdd567892 小时前
GIN索引原理
运维·算法·postgresql