Gin框架操作指南08:日志与安全

官方文档地址(中文):https://gin-gonic.com/zh-cn/docs/

注:本教程采用工作区机制,所以一个项目下载了Gin框架,其余项目就无需重复下载,想了解的读者可阅读第一节:Gin操作指南:开山篇

本节演示日志与安全相关的API,包括定义路由日志的格式;如何记录日志;安全页眉;使用BasicAuth中间件;使用HTTP方法。其中控制日志输出颜色就是将gin.DisableConsoleColor()替换为gin.ForceConsoleColor(),读者可自行尝试,本文不做演示。在开始之前,我们需要在"04日志与安全"目录下打开命令行,执行如下命令来创建子目录:

bash 复制代码
mkdir 定义路由日志的格式 如何记录日志 安全页眉 使用BasicAuth中间件 使用HTTP方法

目录

一、定义路由日志的格式

默认的路由日志格式:

bash 复制代码
[GIN-debug] POST   /foo                      --> main.main.func1 (3 handlers)
[GIN-debug] GET    /bar                      --> main.main.func2 (3 handlers)
[GIN-debug] GET    /status                   --> main.main.func3 (3 handlers)

如果你想要以指定的格式(例如 JSON,key values 或其他格式)记录信息,则可以使用 gin.DebugPrintRouteFunc 指定格式。 在下面的示例中,我们使用标准日志包记录所有路由,但你可以使用其他满足你需求的日志工具。

go 复制代码
package main

import (
	"log"      // 导入标准日志包,用于记录日志信息
	"net/http" // 导入 net/http 包,用于 HTTP 状态码和相关类型

	"github.com/gin-gonic/gin" // 导入 Gin 框架
)

func main() {
	r := gin.Default() // 创建一个默认的 Gin 路由引擎

	// 自定义路由打印函数,以指定的格式记录路由信息
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
		// 使用标准日志包打印每个路由的 HTTP 方法、绝对路径、处理函数名称和处理函数数量
		log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
	}

	// 定义 POST 路由 /foo
	r.POST("/foo", func(c *gin.Context) {
		// 返回 JSON 响应,内容为 "foo" 和 HTTP 状态码 200
		c.JSON(http.StatusOK, "foo")
	})

	// 定义 GET 路由 /bar
	r.GET("/bar", func(c *gin.Context) {
		// 返回 JSON 响应,内容为 "bar" 和 HTTP 状态码 200
		c.JSON(http.StatusOK, "bar")
	})

	// 定义 GET 路由 /status
	r.GET("/status", func(c *gin.Context) {
		// 返回 JSON 响应,内容为 "ok" 和 HTTP 状态码 200
		c.JSON(http.StatusOK, "ok")
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run() // 启动 Gin 路由引擎
}

效果
GET测试

POST测试

二、如何记录日志

go 复制代码
package main

import (
	"io" // 导入 io 包,用于多路写入
	"os" // 导入 os 包,用于文件操作

	"github.com/gin-gonic/gin" // 导入 Gin 框架
)

func main() {
	// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
	gin.DisableConsoleColor()

	// 创建一个文件用于保存日志,文件名为 "gin.log"
	f, err := os.Create("gin.log")
	if err != nil {
		panic(err) // 如果文件创建失败,抛出错误
	}

	// 设置 Gin 的默认写入器,将日志写入到刚创建的文件中
	gin.DefaultWriter = io.MultiWriter(f)

	// 如果需要同时将日志写入文件和控制台,请使用以下代码。
	// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	// 创建默认的 Gin 路由引擎
	router := gin.Default()

	// 定义 GET 路由 /ping
	router.GET("/ping", func(c *gin.Context) {
		// 返回字符串 "pong" 和 HTTP 状态码 200
		c.String(200, "pong")
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	router.Run(":8080") // 启动 Gin 路由引擎
}

效果

注意,启动后控制台是没有输出的,因为屏蔽了颜色,但go.mod旁边会出现gin.log文件,打开即可看到控制台的输出

打开浏览器,测试,log文件会变化

三、安全页眉

使用安全标头保护网络应用程序免受常见安全漏洞的攻击非常重要。本示例将向您展示如何在 Gin 应用程序中添加安全标头,以及如何避免与主机标头注入相关的攻击(SSRF、开放重定向)。

go 复制代码
package main

import (
	"net/http" // 导入 net/http 包,用于 HTTP 相关的功能

	"github.com/gin-gonic/gin" // 导入 Gin 框架
)

func main() {
	// 创建一个默认的 Gin 路由引擎
	r := gin.Default()

	// 设置期望的主机头部(Host Header)
	expectedHost := "localhost:8080"

	// 设置安全标头的中间件
	r.Use(func(c *gin.Context) {
		// 检查请求中的 Host 是否符合预期
		if c.Request.Host != expectedHost {
			// 如果 Host 不正确,返回 400 错误并结束请求
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid host header"})
			return
		}
		// 设置安全标头以保护应用程序
		c.Header("X-Frame-Options", "DENY")                                                                                                                                    // 防止点击劫持
		c.Header("Content-Security-Policy", "default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline';") // 防止跨站脚本攻击
		c.Header("X-XSS-Protection", "1; mode=block")                                                                                                                          // 启用 XSS 保护
		c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")                                                                                  // 强制使用 HTTPS
		c.Header("Referrer-Policy", "strict-origin")                                                                                                                           // 控制引用来源
		c.Header("X-Content-Type-Options", "nosniff")                                                                                                                          // 防止 MIME 类型嗅探
		c.Header("Permissions-Policy", "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()")                 // 控制特定功能的权限
		c.Next()                                                                                                                                                               // 继续处理请求
	})

	// 定义 GET 路由 /ping
	r.GET("/ping", func(c *gin.Context) {
		// 返回 JSON 响应,包含 "pong" 消息
		c.JSON(200, gin.H{
			"message": "pong", // 消息内容
		})
	})

	// 启动服务,监听 0.0.0.0:8080
	r.Run() // listen and serve on 0.0.0.0:8080
}

打开postman,输入http://localhost:8080/ping,此时直接点send,或者随意设置headers中的key和value,只要key不是host(不区分大小写),均能正常输出。但如果在headers中设置了key为host(不区分大小写),那么value就必须是代码中设置好的值,这里是localhost:8080,否则出错,如图:

四、使用BasicAuth中间件

go 复制代码
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// 模拟一些私人数据,使用 map 结构存储用户的私人信息
var secrets = gin.H{
	"foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},     // 用户 foo 的私人信息
	"austin": gin.H{"email": "austin@example.com", "phone": "666"}, // 用户 austin 的私人信息
	"lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},  // 用户 lena 的私人信息
}

func main() {
	r := gin.Default() // 创建一个默认的 Gin 路由器

	// 路由组使用 gin.BasicAuth() 中间件,保护 /admin 路径
	// gin.Accounts 是 map[string]string 的一种快捷方式,设置用户和密码
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
		"foo":    "bar",    // 用户 foo 的密码
		"austin": "1234",   // 用户 austin 的密码
		"lena":   "hello2", // 用户 lena 的密码
		"manu":   "4321",   // 用户 manu 的密码
	}))

	// /admin/secrets 端点处理,只有经过 Basic Auth 验证的用户可以访问
	// 当用户访问 "localhost:8080/admin/secrets" 时,将触发此处理函数
	authorized.GET("/secrets", func(c *gin.Context) {
		// 获取当前用户的信息,它是由 BasicAuth 中间件设置的
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
			// 如果找到了用户的私人信息,返回该信息
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
		} else {
			// 如果未找到用户的私人信息,返回默认信息
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
		}
	})

	// 监听并在 0.0.0.0:8080 上启动服务,等待请求
	r.Run(":8080")
}

效果

五、使用HTTP方法

go 复制代码
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// 获取请求的处理函数
func getting(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "GET request successful"})
}

// 处理 POST 请求的处理函数
func posting(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "POST request successful"})
}

// 处理 PUT 请求的处理函数
func putting(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "PUT request successful"})
}

// 处理 DELETE 请求的处理函数
func deleting(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "DELETE request successful"})
}

// 处理 PATCH 请求的处理函数
func patching(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "PATCH request successful"})
}

// 处理 HEAD 请求的处理函数
func head(c *gin.Context) {
	// HEAD 请求只返回状态和头部,不返回请求体
	c.Status(http.StatusOK) // 返回状态码 200
}

// 处理 OPTIONS 请求的处理函数
func options(c *gin.Context) {
	// OPTIONS 请求返回允许的 HTTP 方法
	c.JSON(http.StatusOK, gin.H{"methods": "GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS"})
}

func main() {
	// 禁用控制台颜色
	// gin.DisableConsoleColor()

	// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
	router := gin.Default()

	// 定义各个 HTTP 方法的路由及其处理函数
	router.GET("/someGet", getting)         // 处理 GET 请求
	router.POST("/somePost", posting)       // 处理 POST 请求
	router.PUT("/somePut", putting)         // 处理 PUT 请求
	router.DELETE("/someDelete", deleting)  // 处理 DELETE 请求
	router.PATCH("/somePatch", patching)    // 处理 PATCH 请求
	router.HEAD("/someHead", head)          // 处理 HEAD 请求
	router.OPTIONS("/someOptions", options) // 处理 OPTIONS 请求

	// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
	router.Run() // 启动服务并监听端口
	// router.Run(":3000") // 如果需要硬编码端口号,可以取消注释这一行
}

效果

使用 Postman 进行测试:

GET 请求:

选择 GET 方法,输入 URL http://localhost:8080/someGet,点击 Send。

POST 请求:

选择 POST 方法,输入 URL http://localhost:8080/somePost,点击 Send。

PUT 请求:

选择 PUT 方法,输入 URL http://localhost:8080/somePut,点击 Send。

DELETE 请求:

选择 DELETE 方法,输入 URL http://localhost:8080/someDelete,点击 Send。

PATCH 请求:

选择 PATCH 方法,输入 URL http://localhost:8080/somePatch,点击 Send。

HEAD 请求:

选择 HEAD 方法,输入 URL http://localhost:8080/someHead,点击 Send。

OPTIONS 请求:

选择 OPTIONS 方法,输入 URL http://localhost:8080/someOptions,点击 Send。

这里只展示一个:

相关推荐
何以解忧,唯有..1 小时前
Go 语言数据类型详解:从基础到复合类型
开发语言·golang·mfc
踏着七彩祥云的小丑1 小时前
Go学习第7天:Map集合 + 递归函数 + 类型转换
开发语言·学习·golang·go
何以解忧,唯有..1 小时前
Go语言变量的声明方式详解
开发语言·后端·golang
寂夜了无痕2 小时前
Go 多版本管理工具G 保姆级安装配置教程
golang·go多版本管理
张忠琳3 小时前
【Go 1.26.4】Golang Slice 深度解析
开发语言·后端·golang
张忠琳17 小时前
【Go 1.26.4】Golang Channel 深度解析
开发语言·后端·golang
张忠琳19 小时前
【Go 1.26.4】Golang Map 深度解析
开发语言·后端·golang
何以解忧,唯有..1 天前
Go 语言安装与环境配置完整指南
开发语言·后端·golang
踏着七彩祥云的小丑1 天前
Go 学习第6天:结构体 + 切片 + range遍历
开发语言·学习·golang·go
浮尘笔记1 天前
Go实现大文件异步流式采集引擎
开发语言·后端·golang