23 go语言(golang) - gin框架安装及使用(四)

五、跨域资源共享

跨域资源共享(CORS,Cross-Origin Resource Sharing)是一种机制,它允许来自不同源的请求访问资源。默认情况下,浏览器出于安全原因会阻止跨域 HTTP 请求。Gin 框架本身没有内置的 CORS 支持,但可以通过中间件轻松实现。

  • 同源策略:浏览器的安全策略,只允许同源请求访问资源。同源指的是协议、主机和端口都相同。

  • CORS 头:服务器通过设置特定的 HTTP 响应头来告诉浏览器它允许哪些来源可以访问其资源。

5.1 不配置CORS

假设你有一个前端应用运行在 http://localhost:3000,而后端服务是用 Gin 开发的,运行在 http://localhost:8080。前端需要通过 AJAX 请求调用后端的 API。

如果没有配置 CORS,当浏览器尝试从 http://localhost:3000 发起请求到 http://localhost:8080 时,会因为跨域限制而被阻止。

5.1.1 后端代码

启动服务

go 复制代码
func Test1(t *testing.T) {
	r := gin.Default()

	r.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "成功进入test路由!",
		})
	})
	r.Run()
}

5.1.2 前端代码(发起跨域请求)

创建一个名为 index.html 的文件,并将以下内容粘贴进去:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fetch API Example</title>
</head>
<body>

<h1>Fetch API Example</h1>

<script>
    // 使用 Fetch API 发起请求到后端接口
    fetch('http://localhost:8080/test')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('Error:', error));
</script>

</body>
</html>

5.1.3 测试

方式一:用浏览器打开index.html

方式二(我用的这种方式):为了更方便地查看和调试前端代码,可以使用 Python 提供的简易 HTTP 服务器。在终端中导航到项目目录,然后运行

python 复制代码
python -m http.server 3000

这会在 http://localhost:3000 启动一个简单的 HTTP 静态文件服务器。

然后再在浏览器访问 http://localhost:3000/index.html

5.1.4 测试结果

打开浏览器开发者工具(通常按 F12 或右键点击页面选择"检查"),切换到"console"标签页

这是因为浏览器默认出于安全考虑,不允许从不同源(不同协议、域名或端口)的页面访问资源。如果服务器未正确设置允许跨域访问的响应头,就会触发这个错误。

Access to fetch at 'http://localhost:8080/test' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

5.2 配置CORS

5.2.1 安装 CORS 中间件库

github.com/gin-contrib/cors 是一个常用的 Gin 中间件库。

安装命令:

bash 复制代码
go get github.com/gin-contrib/cors

5.2.2 配置和使用中间件

最简单的,使用默认的配置

go 复制代码
	// 使用默认选项
	r.Use(cors.Default())

或者自定义配置

go 复制代码
func Test3(t *testing.T) {
	r := gin.Default()

	// 或者自定义配置
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:3000"},
		AllowMethods:     []string{"GET", "POST", "PUT"},
		AllowHeaders:     []string{"Origin", "Content-Type"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	r.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "成功进入test路由!",
		})
	})
	r.Run()
}

配置选项解释

  • AllowOrigins: 指定哪些域名可以进行跨域请求。如果要允许所有域名,可以使用 "*"

  • AllowMethods: 指定哪些 HTTP 方法被允许用于跨域请求,如 GET、POST 等。

  • AllowHeaders: 指定客户端在预检请求(preflight request)时能发送哪些自定义头部字段。

  • ExposeHeaders: 列出哪些响应头部信息可以暴露给外部 JavaScript 程序。

  • AllowCredentials: 是否允许发送 Cookie 信息。如果设置为 true,则不能将 AllowOrigins 设置为 "*",而必须指定具体的 URL。

  • MaxAge: 指示预检请求结果能够被缓存多长时间,以减少客户端与服务器之间不必要的通信。

5.2.3 再次测试结果

六、授权与认证

在Gin框架中,实现授权和认证通常涉及到用户身份验证(Authentication)和权限控制(Authorization)。这两者是确保应用程序安全性的重要组成部分。

6.1 认证(Authentication)

认证是指验证用户身份的过程。常见的方法包括使用用户名和密码、OAuth、JWT等。在Gin中,通常通过中间件来处理认证逻辑。

这里我们使用JWT(JSON Web Token),它是一种用于在各方之间作为JSON对象安全地传输信息的紧凑、URL安全的方式。广泛应用于Web应用程序中,用于实现用户认证和授权。

  1. 用户登录:接收用户名和密码,验证后生成并返回一个JWT。
  2. 请求保护资源:客户端在请求头或Cookie中携带JWT。
  3. 解析与验证JWT:服务器端通过解析该令牌以确认其有效性,并提取其中的声明信息。

6.1.1 安装JWT

官方文档

shell 复制代码
go get -u github.com/golang-jwt/jwt/v5

6.1.2 使用

6.1.2.1 生成新的token

1、首先定义一个登录接口

go 复制代码
type LoginInfo struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Test4(t *testing.T) {
	r := gin.Default()

	// 模拟用户登录操作
	r.POST("login", func(c *gin.Context) {
		var loginInfo LoginInfo
		if err := c.ShouldBindJSON(&loginInfo); loginInfo.Password != "123123" || err != nil {
			// 简单模拟校验密码是否正确
			c.JSON(http.StatusBadRequest, gin.H{
				"msg": "用户名或密码错误",
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"msg": "登录成功",
		})
	})

	r.Run()
}

2、然后实现登录验证成功后生成并返回一个JWT token的方法

go 复制代码
// 定义一个密钥
var jwtKey = []byte("这是个密钥 private key")

// token
type Claims struct {
	Username             string
	jwt.RegisteredClaims // 官方提供的实现了Claims接口的类
}

// 登录成功后生成一个有效时间为5分钟的token
func LoginSuccess(username string) string {
	expirationTime := time.Now().Add(time.Minute * 5)
	claims := &Claims{
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expirationTime),
		},
	}

	// 注册一个新的token,需要传入注册方法和实现Claims接口的类
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	tokenString, _ := token.SignedString(jwtKey)

	fmt.Println(tokenString)
	return tokenString
}

3、应用在login接口中

go 复制代码
		...
		
		// 登录成功后,注册一个token并通过cookie返回给客户
		tokenStr := LoginSuccess(loginInfo.Username)
		c.SetCookie("token", tokenStr, 300, "/", "", false, true)

		c.JSON(http.StatusOK, gin.H{
			"msg": "登录成功",
		})
}

4、调用login接口成功后,应该在cookie中能看到token

6.1.2.2 验证cookie中的token

定义一个gin的中间件,用于校验token,如果校验成功则进行c.Next(),否则不通过

go 复制代码
// 定义校验token的中间件
func CheckToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取cookie中的token
		tokenStr, err := c.Cookie("token")
		if err != nil {
			c.Abort()
			c.JSON(http.StatusUnauthorized, gin.H{
				"msg": "请重新登录",
			})
		}

		// 校验token
		claims := &Claims{}
		tkn, err := jwt.ParseWithClaims(tokenStr, claims,
			func(token *jwt.Token) (interface{}, error) { return jwtKey, nil })

		if !tkn.Valid || err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"msg": "请重新登录",
			})
			return
		}

		// 可以将用户信息放进context中,方便后续使用
		c.Set("username", claims.Username)

		c.Next()
	}
}

定义一个新的接口,并应用这个中间件

go 复制代码
r.Use(CheckToken())
	r.GET("/homepage", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "欢迎你," + c.GetString("username"),
		})
	})

将之前的cookie中的token复制到新的窗口中调用homepage接口

6.2 授权(Authorization)

授权是在确认用户身份之后,决定其是否有权访问特定资源或执行某些操作的过程。常见的方法包括基于角色的访问控制(RBAC)和基于属性的访问控制(ABAC)。

6.2.1 基于角色的访问控制

RBAC根据用户所属角色来授予权限。例如,一个管理员可能有权访问所有资源,而普通用户只能查看自己的数据。

go 复制代码
func CheckRole() gin.HandlerFunc {
	return func(c *gin.Context) {
		username := c.GetString("username")

		if role := getRole(username); role != "admin" {
			c.Abort()
			c.JSON(http.StatusForbidden, gin.H{
				"msg": "抱歉,没有权限访问",
			})
			return
		}

		// 如果有权限,则继续处理请求
		c.Next()
	}
}

func getRole(username string) string {
	// 模拟从数据库查询该用户的角色
	if username == "admin" {
		return "admin"
	} else {
		return "user"
	}
}

然后定义一个新的路由组,应用这个中间件

go 复制代码
  group := r.Group("menu")
	group.Use(CheckRole())
	group.GET("list", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "成功访问菜单",
		})
	})
相关推荐
编码浪子1 小时前
构建一个rust生产应用读书笔记6-拒绝无效订阅者02
开发语言·后端·rust
不爱说话郭德纲1 小时前
基于uniapp使用websocket进行实时通讯
开发语言·前端·javascript·vue.js
咖猫2 小时前
Guava 库中的 `Multimap` 是一个允许一个键对应多个值的集合 Guava `Multimap` 的基本代码示例:
开发语言·python·guava
咖猫2 小时前
Google guava 最佳实践 学习指南之08 `BiMap`(双向映射)
java·开发语言·guava
滿2 小时前
处理错误的两种方式:try...catch 与 then...catch
java·开发语言
SomeB1oody2 小时前
【Rust自学】3.6. 控制流:循环
开发语言·后端·rust
Ttang232 小时前
Tomcat原理(4)——尝试手动Servlet的实现
java·开发语言·servlet·java-ee·tomcat·intellij-idea
菜鸟赵大宝2 小时前
【C++】C++实现字符串大小写转换功能
开发语言·c++
计算机毕设指导63 小时前
基于Springboot林业产品推荐系统【附源码】
java·开发语言·spring boot·后端·mysql·spring·intellij-idea
Teacher_Wyh3 小时前
算法知识-18-STL
开发语言·c++·算法