添加 refreshToken 令牌刷新

common.Time json 序列化 与 反序列化完善
This commit is contained in:
Shikong 2022-10-06 22:12:43 +08:00
parent 362b1ebc39
commit 40f98a050f
8 changed files with 342 additions and 15 deletions

View File

@ -12,8 +12,20 @@ const (
)
// MarshalJSON on Json Time format Time field with %Y-%m-%d %H:%M:%S
func (t Time) MarshalJSON() ([]byte, error) {
func (t *Time) MarshalJSON() ([]byte, error) {
// 重写time转换成json之后的格式
var tmp = fmt.Sprintf("\"%s\"", time.Time(t).Format(timeFormat))
var tmp = fmt.Sprintf("\"%s\"", time.Time(*t).Format(timeFormat))
return []byte(tmp), nil
}
func (t *Time) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
if string(data) == "null" {
return nil
}
// Fractional seconds are handled implicitly by Parse.
var err error
rawT, err := time.Parse(`"`+timeFormat+`"`, string(data))
*t = Time(rawT)
return err
}

View File

@ -31,7 +31,7 @@ func NewController(app *fiber.App) *Controller {
// @Accept json
// @Produce json
// @Param vo body dto.Login true "用户登录"
// @Success 200 {object} response.Response{data=string}
// @Success 200 {object} response.Response{data=vo.Login}
// @Failure default {object} errorx.CodeErrorResponse
// @Router /auth/login [post]
func (c *Controller) Login() {
@ -42,11 +42,39 @@ func (c *Controller) Login() {
return ctx.JSON(err)
}
token, err := auth.Services.Login(login)
result, err := auth.Services.Login(login)
if err = errorx.ParseError(err); err != nil {
return ctx.JSON(err)
}
return ctx.JSON(response.NewResponse(token))
return ctx.JSON(response.NewResponse(result))
})
}
// RefreshToken 刷新令牌
//
// @Summary 刷新令牌
// @Description 刷新令牌
// @Tags Auth
// @Accept json
// @Produce json
// @Param vo body dto.RefreshToken true "刷新令牌"
// @Success 200 {object} response.Response{data=vo.Login}
// @Failure default {object} errorx.CodeErrorResponse
// @Router /auth/refreshToken [post]
func (c *Controller) RefreshToken() {
c.Router.Post("refreshToken", func(ctx *fiber.Ctx) error {
refresh := &dto.RefreshToken{}
err := ctx.BodyParser(refresh)
if err = errorx.ParseError(err); err != nil {
return ctx.JSON(err)
}
result, err := auth.Services.RefreshToken(refresh.RefreshToken)
if err = errorx.ParseError(err); err != nil {
return ctx.JSON(err)
}
return ctx.JSON(response.NewResponse(result))
})
}

View File

@ -60,7 +60,59 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"type": "string"
"$ref": "#/definitions/vo.Login"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/auth/refreshToken": {
"post": {
"description": "刷新令牌",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "刷新令牌",
"parameters": [
{
"description": "刷新令牌",
"name": "vo",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RefreshToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/vo.Login"
}
}
}
@ -642,6 +694,15 @@ const docTemplate = `{
}
}
},
"dto.RefreshToken": {
"type": "object",
"properties": {
"refreshToken": {
"type": "string",
"example": "0123456789ABCDEFG"
}
}
},
"errorx.CodeErrorResponse": {
"type": "object",
"properties": {
@ -742,6 +803,21 @@ const docTemplate = `{
"example": "OK"
}
}
},
"vo.Login": {
"type": "object",
"properties": {
"refreshToken": {
"description": "refreshToken 刷新令牌",
"type": "string",
"example": "0123456789ABCDEFG"
},
"token": {
"description": "token 用户令牌",
"type": "string",
"example": "0123456789ABCDEFG"
}
}
}
},
"securityDefinitions": {

View File

@ -52,7 +52,59 @@
"type": "object",
"properties": {
"data": {
"type": "string"
"$ref": "#/definitions/vo.Login"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/auth/refreshToken": {
"post": {
"description": "刷新令牌",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "刷新令牌",
"parameters": [
{
"description": "刷新令牌",
"name": "vo",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RefreshToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/vo.Login"
}
}
}
@ -634,6 +686,15 @@
}
}
},
"dto.RefreshToken": {
"type": "object",
"properties": {
"refreshToken": {
"type": "string",
"example": "0123456789ABCDEFG"
}
}
},
"errorx.CodeErrorResponse": {
"type": "object",
"properties": {
@ -734,6 +795,21 @@
"example": "OK"
}
}
},
"vo.Login": {
"type": "object",
"properties": {
"refreshToken": {
"description": "refreshToken 刷新令牌",
"type": "string",
"example": "0123456789ABCDEFG"
},
"token": {
"description": "token 用户令牌",
"type": "string",
"example": "0123456789ABCDEFG"
}
}
}
},
"securityDefinitions": {

View File

@ -9,6 +9,12 @@ definitions:
example: "12341234"
type: string
type: object
dto.RefreshToken:
properties:
refreshToken:
example: 0123456789ABCDEFG
type: string
type: object
errorx.CodeErrorResponse:
properties:
code:
@ -78,6 +84,17 @@ definitions:
example: OK
type: string
type: object
vo.Login:
properties:
refreshToken:
description: refreshToken 刷新令牌
example: 0123456789ABCDEFG
type: string
token:
description: token 用户令牌
example: 0123456789ABCDEFG
type: string
type: object
info:
contact:
email: 919411476@qq.com
@ -112,7 +129,7 @@ paths:
- $ref: '#/definitions/response.Response'
- properties:
data:
type: string
$ref: '#/definitions/vo.Login'
type: object
default:
description: ""
@ -121,6 +138,37 @@ paths:
summary: 用户登录
tags:
- Auth
/auth/refreshToken:
post:
consumes:
- application/json
description: 刷新令牌
parameters:
- description: 刷新令牌
in: body
name: vo
required: true
schema:
$ref: '#/definitions/dto.RefreshToken'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/vo.Login'
type: object
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 刷新令牌
tags:
- Auth
/casbin/getUserRoles:
get:
consumes:

View File

@ -1,6 +1,15 @@
package dto
// Login
// @Param account body string true "用户账号(account)"
// @Param password body string true "用户密码"
type Login struct {
Account string `json:"account" example:"root"`
Password string `json:"password" example:"12341234"`
}
// RefreshToken
// @Param refreshToken body string true "刷新令牌"
type RefreshToken struct {
RefreshToken string `json:"refreshToken" example:"0123456789ABCDEFG"`
}

8
model/vo/auth.go Normal file
View File

@ -0,0 +1,8 @@
package vo
type Login struct {
// token 用户令牌
Token string `json:"token" example:"0123456789ABCDEFG"`
// refreshToken 刷新令牌
RefreshToken string `json:"refreshToken" example:"0123456789ABCDEFG"`
}

View File

@ -1,15 +1,26 @@
package auth
import (
"context"
"errors"
"gofiber.study.skcks.cn/common/errorx"
"github.com/goccy/go-json"
"gofiber.study.skcks.cn/common/logger"
"gofiber.study.skcks.cn/common/utils"
"gofiber.study.skcks.cn/global"
"gofiber.study.skcks.cn/model/dto"
"gofiber.study.skcks.cn/model/generic/models"
"gofiber.study.skcks.cn/model/vo"
"time"
)
var (
Failed = errors.New("账号或密码错误")
InvalidRefreshToken = errors.New("刷新令牌已失效")
)
const (
Separator = ":"
RefreshTokenPrefix = "RefreshToken" + Separator
)
type Service struct {
@ -21,7 +32,19 @@ func InitService() {
Services = &Service{}
}
func (s *Service) Login(login *dto.Login) (token string, err error) {
func (s *Service) generateAndSaveRefreshToken(user *models.User) (refreshToken string, err error) {
refreshToken, err = global.GetNanoId()
if err != nil {
return
}
expire := time.Duration(global.Config.Jwt.Expire*2) * time.Second
ctx := context.Background()
global.Redis.Set(ctx, RefreshTokenPrefix+refreshToken, utils.Json(user), expire)
return
}
func (s *Service) Login(login *dto.Login) (result *vo.Login, err error) {
user := &models.User{Account: login.Account, Password: login.Password}
exist, err := global.DataSources.Get(user)
if err != nil {
@ -29,14 +52,61 @@ func (s *Service) Login(login *dto.Login) (token string, err error) {
}
if !exist {
return token, Failed
return nil, Failed
}
token, err = global.GetToken(global.UserClaims{
token, err := global.GetToken(global.UserClaims{
Id: user.Id,
Account: user.Account,
})
err = errorx.ParseError(err)
if err != nil {
return
}
refreshToken, err := s.generateAndSaveRefreshToken(user)
return &vo.Login{
Token: token,
RefreshToken: refreshToken,
}, err
}
func (s *Service) RefreshToken(refreshToken string) (result *vo.Login, err error) {
ctx := context.Background()
data, err := global.Redis.Get(ctx, RefreshTokenPrefix+refreshToken).Result()
if err != nil {
return nil, InvalidRefreshToken
}
global.Redis.Del(ctx, RefreshTokenPrefix+refreshToken)
cache := &models.User{}
err = json.Unmarshal([]byte(data), cache)
if err != nil {
return nil, InvalidRefreshToken
}
user := &models.User{Id: cache.Id, Account: cache.Account}
exist, err := global.DataSources.Get(user)
if !exist {
logger.Log.Infof("未能从 %s 找到用户信息", RefreshTokenPrefix+refreshToken)
return nil, InvalidRefreshToken
}
token, err := global.GetToken(global.UserClaims{
Id: user.Id,
Account: user.Account,
})
if err != nil {
return
}
refreshToken, err = s.generateAndSaveRefreshToken(user)
return &vo.Login{
Token: token,
RefreshToken: refreshToken,
}, err
}