Go 语言特性导致的 Token 无效

场景

假设此时有一个用户表,其中每个用户都会有一些随机生成的字符串Token,预访问后面的功能需要先校验请求包里的Token对应的用户身份。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package web

import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)

var DB *gorm.DB

type UserTab struct {
gorm.Model

Email string `json:"email" gorm:"column:email;not null;type:varchar(128);unique;size:128;"`
Token string `json:"token" gorm:"column:token;index;type:varchar(16);default:null;"`
}

func UserHandler(c *gin.Context) {
token := c.Query("token")
user, err := UserFirst(&UserTab{Token: token})
if err != nil {
c.Status(404)
return
}

c.JSON(200, gin.H{
"user": user,
})
}

func UserFirst(query *UserTab) (*UserTab, error) {
var user UserTab
err := DB.Where(query).First(&user).Error
return &user, err
}

Gorm 支持使用一个数据库Model指针的形式生成查询语句,比如&User{Token: token},在token的值不为空时,生成的SQL语句将会是:

1
SELECT * FROM `user` WHERE `token` = 'your-token' LIMIT 1

分析

上面代码对于token的校验几乎是形同虚设了,主要原因是 Go 语言的一些特性。

Go 语言中对于变量声明后,存在默认值:

整形如int8、byte、int16、uint、uintprt等,默认值为0。

浮点类型如float32、float64,默认值为0。

布尔类型bool的默认值为false。

字符串string的默认值为”“。

错误类型error的默认值为nil。

但是 Go 语言是无法分辨结构体中某个字符是否被赋值过。例如 token 是一个字符类型,默认赋值为空,如果用户传入的 token 也是空,那么 Go 无法分辨这个 token 值为空是因为声明后自动赋值造成的还是用户输入的就是空字符。

由于是生成的查询语句,如果 token 为空后面内容会被自动忽略,因此 SQL 语句就变成了:

1
SELECT * FROM `user` LIMIT 1

直接会返回第一个用户的信息,一般是管理员的。

所以在查询前一定要进行判断 token 是否为空。

DB.Where(&User{Token: token}).First(&user).Error