Auth01|常见三种登录认证机制

本文主要记录曾处理登录场景中的经常用到的三种的认证机制。

在单体架构中,基本的认证过程都是简单的账号密码登录模式。以下主要基于Cookie进行身份验证的场景。

基本流程:

  • 用户在输入账号密码确认登录,
  • 由服务端对账号密码和数据库的存储的数据进行核验,确认存在且密码一致则为登录成功,
  • 登录成功后返回用户信息。同时会将生成sessionid,同时将返回的用户信息存储在cookie中。
  • 后续用户的所有的请求都会携带Cookie验证身份

像这种简单的单体架构中,用户量都比较少(<200),QPS也是比较低(<50)。适用在小系统或者内部系统的使用中。

功能实现:获取到账号和密码,直接和数据库匹配,如果匹配成功则登录成功。

go 复制代码
//登录验证
func LoginHandler(c *gin.Context) {
    var cred struct{ Username, Password string }
    if err := c.BindJSON(&cred); err != nil { /* 错误处理 */ }
    
    var storedHash string
    err := db.QueryRow("SELECT password_hash FROM users WHERE username=?", cred.Username).
        Scan(&storedHash)
    
    if err == sql.ErrNoRows || bcrypt.CompareHashAndPassword(
        []byte(storedHash), []byte(cred.Password)) != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{"message": "登录成功"})
}

登录成功后,需要保证用户登录状态。常见的解决方案是 Cookie 机制,Session 会话管理以及Token机制。

  • Cookie 机制:登录成功后将登录凭证加密并写入浏览器的Cookie 。后续客户端请求时携带Cookie,服务端解析Cookie 内容再去验证内容有效性。
  • Session 会话管理:登录成功后服务器生成唯一的sessionId,存储在服务端并返回给客户端。客户端将其存储在 Cookie 或其他存储机制中,后续请求携带该标识符,服务器验证其有效性来判断登录状态。
  • Token 机制:用户登录成功后生成Token(常见JWT),并返回给客户端。客户端在用户的后续请求中都需要携带 Token ,以便服务端验证 Token 的有效性。

存在问题:

  • Cookie 机制不安全,如果被拦截破解就基本能获取到相关系统隐私信息
  • 服务端再次验证客户端请求时所携带的信息,可能还需要解析信息,然后根据信息查数据库进行校验有效性确定登录状态,如果在高并发的场景中可能导致占用大量内存
  • 在分布式或集群的环境下,Session机制存在共享的问题

基于Redis 和 Session 的认证

功能实现:

  • 用户登录成功后,生成SessionId 并存储在 Redis 中。其中redis中存储登录用户信息
  • 用户的后续请求,服务端会先获取SessionId 标识然后从 redis 中获取到相关信息
go 复制代码
// 登录成功后
func AddSession(c *gin.Context, userID int) {
    sessionID := uuid.New().String()
    // 存储Session到Redis (24小时过期)
    if err := redisClient.SetEX(ctx, "session:"+sessionID, userID, 
        24*time.Hour).Err(); err != nil {
        // 错误处理
    }
    // 设置HTTP-only Cookie
    c.SetCookie("SESSION_ID", sessionID, 86400, "/", "yourdomain.com", false, true)
}

// 认证中间件
func SessionAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        sessionID, err := c.Cookie("SESSION_ID")
        if err != nil { /* 重定向到登录 */ }
        
        userID, err := redisClient.Get(ctx, "session:"+sessionID).Int()
        if err == redis.Nil { /* Session过期 */ }
        
        c.Set("userID", userID)
        c.Next()
    }
}

基于 Token 的认证

随着微服务架构的广泛应用,相比较替代使用session, 具有相对的优势:

  • 扩展性强, jwt 中的payload 包含用户身份和生命,且无需存储会话数据
  • 通过 http header 传输不依赖Cookie ,避免CSRF攻击
  • session 需要Cookie支持,所以对于原生App 或跨域场景处理有点复杂
  • 性能优化:无需存储token ,减少数据库查询,提升性能

功能实现:

  • 用户登录后,需要同时生成JWT
  • 用户的其他请求都需要校验 Token 的有效性
go 复制代码
// 生成安全JWT(EdDSA算法)
func GenerateJWT(userID int) (string, error) {
    claims := jwt.MapClaims{
        "user_id": userID,
        "exp": time.Now().Add(2 * time.Hour).Unix(),
        "iss": "auth_service",
    }
    token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
    privateKey := loadEd25519PrivateKey() // 从安全存储加载
    return token.SignedString(privateKey)
}

// JWT验证中间件
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenStr := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
        publicKey := loadEd25519PublicKey()
        
        token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
            if _, ok := t.Method.(*jwt.SigningMethodEd25519); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
            }
            return publicKey, nil
        })
        
        if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
            c.Set("userID", claims["user_id"])
            c.Next()
        } else {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
        }
    }
}

使用 JWT 生成Token ,还需要其他的工作需要处理和考虑:

  • Token 刷新机制,使用双Token 设计方案,accessToken 用于业务请求,refreshToken 用于刷新AccessToken。需要注意,刷新accessToken时refeshToken 也要重新生成,refreshToken 绑定用户信息(设备、IP、用户ID ),避免跨设备连用
  • 用户退出场景时删除 Token ,
  • 检测用户操作异常时,强制重新登录,比如频繁登录的情况
  • 当用户角色变化时,需强制token刷新
相关推荐
Justin3go5 小时前
HUNT0 上线了——尽早发布,尽早发现
前端·后端·程序员
Tony Bai6 小时前
高并发后端:坚守 Go,还是拥抱 Rust?
开发语言·后端·golang·rust
一线大码6 小时前
SpringBoot 3 和 4 的版本新特性和升级要点
java·spring boot·后端
weixin_425023007 小时前
Spring Boot 配置文件优先级详解
spring boot·后端·python
weixin_425023007 小时前
Spring Boot 实用核心技巧汇总:日期格式化、线程管控、MCP服务、AOP进阶等
java·spring boot·后端
一线大码7 小时前
Java 8-25 各个版本新特性总结
java·后端
VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
To Be Clean Coder8 小时前
【Spring源码】通过 Bean 工厂获取 Bean 的过程
java·后端·spring
weixin199701080168 小时前
闲鱼 item_get - 商品详情接口对接全攻略:从入门到精通
java·后端·spring
自己的九又四分之三站台9 小时前
导入数据到OG GraphQL以及创建graph
java·后端·graphql