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

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

相关推荐
布莱克6058 小时前
MySQL 增删改查(CRUD)语法详解
数据库·mysql·增删改查
随意起个昵称8 小时前
【单调队列优化DP】U301134 绿色通道
算法·动态规划
Dovis(誓平步青云)8 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
Nil2088 小时前
leetcode 739每日温度
算法·leetcode·职场和发展
蜗牛互联网8 小时前
从Barclays扩大Claude部署看受监管软件工程的责任重分配
java·大数据·人工智能·后端·软件工程
lvwangshu8 小时前
势能分析(摊还分析)
数据结构·数学·算法
JieDavid9 小时前
奇智创达知识产权管理系统期限监控模块实操,告别人工期限疏漏,实现管理闭环!
大数据·运维·人工智能·经验分享·重构
2501_933670799 小时前
2027届大数据管理与应用求职数字化转型顾问:项目、SQL、业务理解怎么排优先级
数据库·sql
盘古开天16669 小时前
PPO算法代码实战(三):PyTorch从零实现PPO求解CartPole
人工智能·pytorch·算法
yyy(十一月限定版)9 小时前
CF2138A Cake Assignment
算法