User information is a sensitive interface that must ensure it can only be accessed after the user has logged in. Similarly, interfaces related to users editing the word library also need authentication. It is not feasible to write the same code for authentication before every interface that requires it. Hence, a middleware/interceptor should be developed to uniformly verify if the Token
is valid.
Middleware/interceptors are functions or components that handle
HTTP
requests and responses. They are typically used to perform certain actions before the request reaches the final handler or before the response is sent to the client.
Auth Middleware
GoFrame
provides an elegant way to control requests using middleware, which is also the mainstream method provided by WebServer
for controlling the request flow, making it a flexible and powerful plugin mechanism based on middleware design.
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()
}
The code r.Middleware.Next()
is the core control of the middleware. Placing it at the end of the function makes it a pre-middleware, meaning it's called before the request. Placing it at the beginning makes it a post-middleware, effective after the request.
r.Header.Get("Authorization")
retrieves the Authorization
field from the HTTP Header
, i.e., the Token
passed from the frontend. After parsing the Token
with jwt.Parse
, it uses token.Valid
to verify its validity. If it invalidates, it returns an HTTP StatusForbidden 403
status code, indicating insufficient permissions. Otherwise, it calls r.Middleware.Next()
to proceed.
The written middleware is reserved and will be registered uniformly after interface development is complete. Next, it follows the pleasant triple-axe rule.
Add 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:"Get Information" tags:"User"`
}
type InfoRes struct {
Username string `json:"username" dc:"Username"`
Email string `json:"email" dc:"Email"`
CreatedAt *gtime.Time `json:"created" dc:"Creation Time"`
UpdatedAt *gtime.Time `json:"update" dc:"Update Time"`
}
The InfoRes
structure defines four response data fields, where the *gtime.Time
data type is a framework time type provided by the gtime
component.
Write 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
}
In Logic
, you cannot directly access the HTTP
object; instead, use g.RequestFromCtx(ctx).Request
to obtain it from the context. After obtaining the Token
, parse out the user ID
, and call the Scan
method to assign the query result to the entity.Users
structure.
The Scan
method is a powerful one that automatically recognizes and converts based on the given parameter type, commonly used in data query operations.
Controller Calls 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
}
Register New Controller
Use group.Group
to add a new route group and group.Middleware
to register the Auth
middleware. All controllers under this group need authentication before accessing.
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
},
}
)
API Testing
Remember to replace Authorization
with your own:
$ 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"
}
}