Go语言中的安全认证:编写无漏洞代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Go中的认证不是儿戏?

\n\n

Go——一种因速度和可靠性而被选用的语言。但即使是最快的服务器,如果密码能被窃取,也会变得毫无价值。Go应用中的安全认证不仅仅是“安装一个库就完事了”。这是一门完整的科学:如何存储密码,如何不搞砸JWT,如何在一切已经紧急时防御CSRF。

\n\n

在本文中,我们不会进行理论讲座。我们将编写代码。真实、可运行的代码,可以直接插入到您的项目中。我们将剖析安全的三大支柱:密码哈希(bcrypt)、JWT令牌处理以及跨站请求伪造(CSRF)防护。

\n\n

正确哈希密码:为什么md5是邪恶的

\n\n

迈向安全的第一步——永远,听好了,永远不要以明文形式存储密码。即使您信任自己的数据库。黑客攻击可能发生在任何人身上(问问Yahoo或Adobe就知道了)。正确的哈希处理就是您的盾牌。

\n\n

在Go中,标准选择是golang.org/x/crypto/bcrypt。为什么是bcrypt?它能抵抗暴力攻击(包含“盐”并减慢计算速度)。MD5或SHA-1可以在微秒内计算完成。Bcrypt则需要数百毫秒。对于攻击者来说这是一场灾难,对用户来说则毫无察觉。

\n\n

示例:用户注册

\n\n
package main\n\nimport (\n    "fmt"\n    "golang.org/x/crypto/bcrypt"\n)\n\nfunc hashPassword(password string) (string, error) {\n    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n    return string(bytes), err\n}\n\nfunc checkPasswordHash(password, hash string) bool {\n    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n    return err == nil\n}\n\nfunc main() {\n    password := "mySecretPass123"\n    hash, _ := hashPassword(password)\n\n    fmt.Println("密码:", password)\n    fmt.Println("哈希值:  ", hash)\n\n    match := checkPasswordHash(password, hash)\n    fmt.Println("匹配:", match)\n}
\n\n

请注意:bcrypt.DefaultCost 是10次迭代。对于生产环境,可以增加到12-14,但不要过度,否则服务器会在负载下崩溃。请在您的硬件上进行测试。

\n\n

登录时密码验证

\n\n

很简单:从表单获取密码,从数据库获取哈希值,通过bcrypt.CompareHashAndPassword进行比较。如果返回nil,则用户身份正确。如果出错,则返回401。

\n\n
func loginHandler(w http.ResponseWriter, r *http.Request) {\n    // 从表单获取密码\n    storedHash := getUserHashFromDB(r.FormValue("email"))\n    if !checkPasswordHash(r.FormValue("password"), storedHash) {\n        http.Error(w, "密码错误", http.StatusUnauthorized)\n        return\n    }\n    // 创建会话或令牌\n}
\n\n

JWT:带有陷阱的轻量级认证

\n\n

用户输入密码后,需要发放一个“通行证”。一种流行的方法是JWT(JSON Web Token)。这是一个紧凑的令牌,包含用户数据和签名。但魔鬼在细节中:不正确的JWT实现可能会使您的应用变得脆弱。

\n\n

在Go中正确生成JWT

\n\n

使用库github.com/golang-jwt/jwt/v5。永远不要自己编写加密相关的轮子——只使用经过验证的库。

\n\n
import (\n    "time"\n    "github.com/golang-jwt/jwt/v5"\n)\n\nvar jwtKey = []byte("my_secret_key_which_must_be_long_and_random")\n\ntype Claims struct {\n    Username string `json:"username"`\n    jwt.RegisteredClaims\n}\n\nfunc generateToken(username string) (string, error) {\n    expirationTime := time.Now().Add(15 * time.Minute)\n    claims := &Claims{\n        Username: username,\n        RegisteredClaims: jwt.RegisteredClaims{\n            ExpiresAt: jwt.NewNumericDate(expirationTime),\n        },\n    }\n\n    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n    return token.SignedS                

博客

书籍推荐