跳到主要内容
版本:2.8.x(Latest)

用户信息是一个敏感的接口,它必须要保证用户登录之后才能访问。同样的,用户编辑单词库相关的接口也需要权限认证。我们不可能在每个需要认证的接口前都编写同样的代码用以权限认证。所以要编写一个中间件/拦截器来统一验证Token是否有效。

中间件/拦截器是处理 HTTP 请求和响应的函数或组件。它们通常用于在请求到达最终处理器之前或响应发送给客户端之前执行一些操作。

Auth 中间件


GoFrame 提供了优雅的中间件请求控制方式, 该方式也是主流的 WebServer 提供的请求流程控制方式, 基于中间件设计可以为 WebServer 提供更灵活强大的插件机制。

internal/logic/middleware/auth.go

package middleware  

import (
"net/http"

"github.com/gogf/gf/v2/net/ghttp"
"github.com/golang-jwt/jwt/v5"
"star/utility"
)

func Auth(r *ghttp.Request) {
var (
jwtKey = utility.JwtKey
tokenString = r.Header.Get("Authorization")
)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
r.Response.WriteStatus(http.StatusForbidden)
r.Exit()
}

r.Middleware.Next()
}

r.Middleware.Next() 这段代码是中间件的控制核心,放在函数的最后面表示它是一个前置中间件,代表请求前调用。放在最前面则是后置中间件,在请求后生效。

r.Header.Get("Authorization")HTTP Header中获取Authorization字段,即获取前端传过来的Tokenjwt.Parse 解析Token后再通过token.Valid验证是否有效,如果失效则返回 HTTP StatusForbidden 403 状态码,即权限不足,反之调用r.Middleware.Next()进入下一步。

编写好的中间件留用,等接口开发完成后统一注册到cmd中。接下来又是愉快的三板斧法则。

添加Api


api/account/v1/account.go

package v1  

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

type InfoReq struct {
g.Meta `path:"account/info" method:"get" sm:"获取信息" tags:"用户"`
}

type InfoRes struct {
Username string `json:"username" dc:"用户名"`
Email string `json:"email" dc:"邮箱"`
CreatedAt *gtime.Time `json:"created" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"update" dc:"更新时间"`
}

InfoRes结构体定义了四个响应数据,其中*gtime.Time数据类型是gtime组件提供的框架时间类型。

编写Logic


internal/logic/users/account.go

package users  

import (
"context"
"errors"
"time"

"github.com/gogf/gf/v2/frame/g"
"github.com/golang-jwt/jwt/v5"
"star/internal/dao"
"star/internal/model/entity"
"star/utility"
)

...

func Info(ctx context.Context) (user *entity.Users, err error) {
user = new(entity.Users)
tokenString := g.RequestFromCtx(ctx).Request.Header.Get("Authorization")

tokenClaims, _ := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return utility.JwtKey, nil
})

if claims, ok := tokenClaims.Claims.(*UserClaims); ok && tokenClaims.Valid {
err = dao.Users.Ctx(ctx).Where("id", claims.Id).Scan(&user)
}
return
}

Logic中不能直接获取HTTP对象,需要使用g.RequestFromCtx(ctx).Request从上下文中获取。获取Token后解析出用户ID,调用Scan 方法将查询结果赋值给entity.Users结构体。

Scan方法是一个非常强大的方法,它会根据给定的参数类型自动识别并转换,是数据查询操作中的常客。

Controller调用Logic


internal/controller/account/account_v1_info.go

package account  

import (
"context"

"star/api/account/v1"
"star/internal/logic/users"
)

func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoRes, err error) {
user, err := users.Info(ctx)
if err != nil {
return nil, err
}
return &v1.InfoRes{
Username: user.Username,
Email: user.Email,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}, nil
return
}

注册新的控制器


使用group.Group新增一个路由分组,并使用group.Middleware注册Auth中间件,凡是在本组下的控制器在访问前都需要经过认证。

internal/cmd/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"
"star/internal/controller/account"
"star/internal/controller/users"
"star/internal/logic/middleware"
)

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()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Group("/v1", func(group *ghttp.RouterGroup) {
group.Bind(
users.NewV1(),
)
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Auth)
group.Bind(
account.NewV1(),
)
})
})
})
s.Run()
return nil
},
}
)

接口测试


注意把Authorization换成自己的:

$ curl -H "Authorization: eyJhbGci...W6Ed_d3P77Mc" http://127.0.0.1:8000/v1/account/info

{
"code":0,
"message":"",
"data":{
"username":"oldme",
"email":"tyyn1022@gmail.com",
"created":"2024-11-08 17:02:16",
"update":"2024-11-08 17:02:16"
}
}