JWT鉴权与日志记录中间件的实现学习笔记 | 青训营

330 阅读6分钟

本文将详细介绍如何使用Go语言中的Gin框架来实现JWT(JSON Web Token)鉴权和日志记录中间件。我们将逐步介绍每个组件的用法,并将最终构建一个完整的应用程序,具备用户鉴权和请求日志记录功能。

1. 介绍

什么是中间件

当构建Web应用程序时,中间件是一种常见的开发模式,它允许您在请求到达处理程序之前或之后执行一些逻辑。中间件可以用于添加功能、处理请求、验证鉴权、记录日志等。以下是一些常见的中间件类型及其介绍:

  1. 身份验证和授权中间件: 这种中间件用于验证用户的身份并授权其访问受保护的资源。例如,JWT鉴权中间件就是一种身份验证和授权中间件,它在请求到达处理程序之前验证请求中的JWT令牌,以确保用户具有访问权限。

  2. 日志记录中间件: 日志记录中间件用于记录请求的详细信息,以便于监控、调试和追踪问题。这些中间件可以记录请求的路径、HTTP方法、状态码、处理时间等信息。日志记录有助于了解应用程序的运行情况,同时也是排查问题的重要工具。

  3. 错误处理中间件: 错误处理中间件用于捕获处理程序中的错误,并根据错误类型返回相应的错误响应。这有助于在应用程序出现异常时提供友好的错误消息,并避免暴露敏感信息给用户。

  4. 缓存中间件: 缓存中间件用于缓存处理程序的输出,以避免重复计算相同的数据。这可以提高应用程序的性能和响应速度。常见的缓存中间件可以将数据存储在内存中,如Redis、Memcached等。

  5. 请求转换中间件: 请求转换中间件用于修改或转换请求的内容,例如解析请求体中的JSON数据,将查询参数转换为结构体等。这种中间件有助于将请求的数据格式转换为处理程序更易于处理的形式。

  6. 响应转换中间件: 类似于请求转换中间件,响应转换中间件用于修改或转换处理程序的响应内容。例如,将处理程序返回的数据转换为特定的格式(如JSON或XML)。

  7. 跨域资源共享(CORS)中间件: CORS中间件用于处理跨域请求,允许不同域名下的前端应用程序访问您的API。这种中间件可以设置响应头来允许或限制跨域请求。

  8. 安全中间件: 安全中间件用于增强应用程序的安全性,例如添加HTTP头来防止跨站点脚本(XSS)攻击、点击劫持等。这可以保护应用程序免受常见的Web安全威胁。

  9. 限流中间件: 限流中间件用于限制特定用户或IP地址的请求频率,以防止恶意用户或流量对应用程序造成压力。这可以确保应用程序的稳定性和可用性。

  10. 监控中间件: 监控中间件用于收集应用程序的运行指标和性能数据,以便进行分析和优化。这可以帮助您了解应用程序的资源使用情况、请求处理时间等信息。

以上仅是一些常见的中间件类型,实际上,中间件可以根据您的需求和应用场景进行扩展和定制。使用中间件可以使您的应用程序更加模块化、可维护和可扩展。

什么是JWT鉴权和日志记录中间件?

JWT(JSON Web Token)是一种用于安全传输信息的开放标准,可以通过令牌在不同系统之间传递信息。在Web应用程序中,JWT常用于鉴权和授权,允许用户在不重复验证的情况下访问受保护的资源。日志记录中间件则用于记录请求的详细信息,以便于调试、监控和追踪问题。

2. 项目准备

环境搭建和依赖安装

在开始之前,请确保您已经安装了Go语言的开发环境。然后,您可以使用以下命令安装所需的依赖库:

go get -u github.com/gin-gonic/gin
go get -u github.com/dgrijalva/jwt-go

目录结构

在项目根目录下创建以下文件和文件夹:

project-root/
│
├── main.go
├── auth/
│   ├── jwt.go
│   └── middleware.go
│
├── logging/
│   └── middleware.go
│
└── utils/
    ├── database.go
    └── redis.go

3. JWT鉴权中间件实现

现在,让我们来实现JWT鉴权中间件,以确保只有经过身份验证的用户才能访问受保护的资源。

生成和解析JWT令牌

auth/jwt.go文件中:

package auth

import (
    "strconv"
    "time"
    "github.com/dgrijalva/jwt-go"
)

var jwtKey = []byte("your-secret-key")

func GenerateToken(userID uint) (string, error) {
    expirationTime := time.Now().Add(24 * time.Hour) // Token有效期
    claims := &jwt.StandardClaims{
        ExpiresAt: expirationTime.Unix(),
        Subject:   strconv.Itoa(int(userID)),
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err

 := token.SignedString(jwtKey)
    if err != nil {
        return "", err
    }

    return tokenString, nil
}

func ParseToken(tokenString string) (*jwt.StandardClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })

    if err != nil {
        return nil, err
    }

    if claims, ok := token.Claims.(*jwt.StandardClaims); ok && token.Valid {
        return claims, nil
    }

    return nil, jwt.ErrInvalidKey
}

实现JWT鉴权中间件

auth/middleware.go文件中:

package auth

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization token is missing"})
            c.Abort()
            return
        }

        claims, err := ParseToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        // 根据需要,您可以在这里使用claims中的信息进行权限验证,例如从数据库中检查用户角色等

        c.Next()
    }
}

应用中间件到需要鉴权的路由

main.go文件中:

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "your-project/auth"
    "your-project/utils"
)

func main() {
    router := gin.Default()

    // 初始化数据库和Redis连接
    db, err := utils.InitDB()
    if err != nil {
        panic(err)
    }
    defer db.Close()

    client := utils.InitRedis()
    defer client.Close()

    // 应用JWT鉴权中间件
    router.Use(auth.AuthMiddleware())

    // 添加需要JWT鉴权的路由
    router.GET("/protected", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "Access granted"})
    })

    router.Run(":8080")
}

4. 日志记录中间件实现

现在,我们将实现日志记录中间件,以记录每个请求的详细信息。

创建日志记录中间件

logging/middleware.go文件中:

package logging

import (
    "fmt"
    "net/http"
    "time"
    "github.com/gin-gonic/gin"
)

func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()

        c.Next()

        latency := time.Since(start)
        status := c.Writer.Status()
        path := c.Request.URL.Path
        method := c.Request.Method

        logMessage := fmt.Sprintf("| %3d | %13v | %15s | %s  %s\n",
            status,
            latency,
            c.ClientIP(),
            method,
            path,
        )

        // 将日志信息输出到控制台
        fmt.Print(logMessage)
    }
}

记录请求信息

main.go文件中:

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "your-project/auth"
    "your-project/logging"
    "your-project/utils"
)

func main() {
    router := gin.Default()

    // 初始化数据库和Redis连接
    db, err := utils.InitDB()
    if err != nil {
        panic(err)
    }
    defer db.Close()

    client := utils.InitRedis()
    defer client.Close()

    // 应用JWT鉴权中间件
    router.Use(auth.AuthMiddleware())

    // 应用日志记录中间件
    router.Use(logging.LoggerMiddleware())

    // 添加需要JWT鉴权的路由
    router.GET("/protected", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "Access granted"})
    })

    router.Run(":8080")
}

通过结合JWT鉴权和日志记录中间件,我们已经构建了一个具有用户鉴权和请求日志记录功能的应用程序。

现在,当用户访问受保护的路由时,将首先经过JWT鉴权中间件。如果请求中包含有效的JWT令牌,用户将被授权访问资源。同时,日志记录中间件将记录请求的详细信息,以便于后续的调试和监控。