TCP 长连接服务:登录注册认证体系实战指南
在 IM 即时通讯、游戏服务、物联网设备通信等 TCP 长连接场景中,连接准入认证是服务安全的第一道防线。
我们需要实现一套「先认证、后业务」的流程:客户端 TCP 连接建立后,不直接开放业务能力,必须完成注册 / 登录并通过校验,才能进入正常的协议交互流程。
整体流程与核心设计原则
核心流程(三步式)
plaintext
连接接入:客户端TCP建联成功 → 服务端不标记在线,仅返回认证提示
认证阶段:客户端发送注册/登录命令 → 服务端校验合法性 → 完成数据库操作
业务阶段:认证通过 → 标记用户在线、设置认证状态 → 进入正常协议/命令处理流程
核心设计原则
- 状态隔离:用认证标记严格区分「未认证连接」和「已认证连接」,未认证连接仅能处理注册 / 登录命令
- 安全优先:密码全程不落地明文,用 bcrypt 单向哈希存储与校验
- 职责分层:连接处理、认证逻辑、数据库操作严格分层,避免代码耦合
- 边界校验:全流程做输入合法性、重复登录、资源初始化检查,避免异常与安全漏洞
核心数据结构设计
内存用户连接结构(连接状态管理)
用于管理单条 TCP 连接的生命周期与状态,核心是认证状态标记,这是分阶段处理的核心依据。
go
type User struct {
Conn net.Conn // 底层TCP连接
Name string // 用户名(认证后赋值)
Authenticated bool // 【核心标记】是否通过认证
mu sync.RWMutex// 读写锁,保护状态并发修改
}
// 标记用户在线(认证成功后调用)
func (u *User) Online() {
u.mu.Lock()
defer u.mu.Unlock()
// 逻辑:加入全局在线用户列表
}
// 标记用户离线(连接断开时调用)
func (u *User) Offline() {
u.mu.Lock()
defer u.mu.Unlock()
// 逻辑:从全局在线用户列表移除
}
- 核心语法说明:
Authenticated布尔值是整个流程的开关,未认证时所有业务命令都会被拦截 - 并发安全:用户状态修改必须加锁,避免多 goroutine 并发修改导致的状态异常
数据库持久化模型(用户数据存储)
用于存储用户的核心数据,重点是密码哈希字段,绝对不存储明文密码。
go
// User 数据库用户表模型
type User struct {
gorm.Model
Username string `gorm:"type:varchar(50);uniqueIndex;not null"` // 唯一用户名
PasswordHash string `gorm:"type:varchar(255);not null"` // 密码哈希(非明文)
IsOnline bool `gorm:"default:false"` // 在线状态
}
字段设计说明:
uniqueIndex给用户名加唯一索引,避免重复注册PasswordHash字段长度预留 255 位,适配 bcrypt 哈希结果的固定长度IsOnline同步用户在线状态,方便业务层查询
密码安全核心:bcrypt 单向加密
密码安全是认证体系的核心,我们使用行业标准的 bcrypt 算法实现密码的单向哈希,无法从哈希结果反推明文密码,彻底避免明文密码泄露风险。
bcrypt 仅需两个核心 API,即可完成密码加密与校验,无需复杂配置。
密码哈希生成(注册时使用)
go
// 生成密码哈希:明文密码 → 不可逆哈希字符串
// 参数1:明文密码的字节数组
// 参数2:哈希成本(推荐用默认值10,数值越高越安全,耗时也越长)
hashBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
passwordHash := string(hashBytes) // 存入数据库的最终结果
密码哈希比对(登录时使用)
go
// 比对明文密码与数据库存储的哈希值
// 参数1:数据库中存储的密码哈希
// 参数2:用户登录输入的明文密码
// 返回值:比对成功返回nil,失败返回错误
err := bcrypt.CompareHashAndPassword([]byte(dbHash), []byte(inputPassword))
- 核心优势:每次生成的哈希结果都不同,但可以正确比对同一明文密码,避免彩虹表攻击
- 开发规范:注册、登录的密码处理必须用这两个 API,绝对禁止明文比对、明文存储
DAO 数据持久化层设计
DAO 层(数据访问层)封装所有数据库操作,实现业务逻辑与数据操作的解耦,同时统一做数据库初始化检查,避免空指针 panic。
我们仅需 3 个核心方法,即可覆盖认证全流程的数据库操作:
创建用户(注册场景)
负责将用户信息写入数据库,自动完成密码哈希,仅暴露极简的入参。
go
// 入参:用户名、明文密码
// 返回:创建成功的用户模型、错误信息
func CreateUser(username, password string) (*User, error) {
// 前置检查:数据库是否初始化
if DB == nil {
return nil, errors.New("数据库未初始化")
}
// 密码哈希生成(见上一节)
hashBytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
// 写入数据库
user := &User{Username: username, PasswordHash: string(hashBytes)}
err := DB.Create(user).Error
return user, err
}
按用户名查询用户(登录场景)
登录时通过用户名查询用户的完整信息,用于后续密码比对。
go
func GetUserByName(username string) (*User, error) {
if DB == nil {
return nil, errors.New("数据库未初始化")
}
var user User
// 按唯一用户名查询单条记录
err := DB.Where("username = ?", username).First(&user).Error
return &user, err
}
更新用户在线状态
认证成功 / 连接断开时,同步更新数据库中的在线状态。
go
func UpdateUserStatus(username string, isOnline bool) error {
if DB == nil {
return nil, errors.New("数据库未初始化")
}
// 仅更新is_online字段,不影响其他数据
return DB.Model(&User{}).Where("username = ?", username).Update("is_online", isOnline).Error
}
TCP 连接处理与认证核心逻辑
这是整个体系的入口,核心是分阶段处理:未认证的连接仅能处理认证命令,认证通过后才开放业务能力。
连接入口:TCP Handler
客户端建联后,首先进入这个处理函数,负责连接生命周期管理与初始响应。
go
func Handler(conn net.Conn) {
// 初始化用户连接对象,初始状态为「未认证」
user := &User{Conn: conn}
// 连接关闭时的兜底处理:标记离线、关闭连接
defer func() {
user.Offline()
conn.Close()
}()
// 给客户端发送认证提示
conn.Write([]byte("欢迎!请输入 register|用户名|密码 或 login|用户名|密码\n"))
// 按行循环读取客户端发送的数据
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
rawLine := strings.TrimSpace(scanner.Text())
if rawLine == "" {
continue
}
// 【核心分阶段逻辑】
if !user.Authenticated {
// 未认证:仅处理注册/登录命令
err := handleAuthCommand(user, rawLine)
if err != nil {
conn.Write([]byte(fmt.Sprintf("认证失败:%v\n", err)))
} else {
conn.Write([]byte("认证成功!\n"))
}
} else {
// 已认证:进入正常业务协议处理流程
processBusinessCommand(user, rawLine)
}
}
}
- 核心逻辑:通过
user.Authenticated标记,把连接的生命周期拆成两个完全隔离的阶段,避免未认证连接访问业务接口 - 语法说明:
bufio.NewScanner按行读取 TCP 数据流,适配文本格式的认证命令,简单易用
认证命令解析:handleAuthCommand
封装注册 / 登录的前置校验与命令分发,是认证流程的统一入口。
go
func handleAuthCommand(user *User, rawLine string) error {
// 按|分割命令,最多分割3段(避免密码中包含|被错误分割)
parts := strings.SplitN(rawLine, "|", 3)
if len(parts) < 3 {
return errors.New("格式错误,示例:register|张三|123456")
}
// 提取命令、用户名、密码
cmd := strings.TrimSpace(parts[0])
username := strings.TrimSpace(parts[1])
password := strings.TrimSpace(parts[2])
// 【前置校验1】用户名合法性:禁止空白、|、制表符等特殊字符
if username == "" || strings.ContainsAny(username, " |\t\n") {
return errors.New("用户名格式非法")
}
// 【前置校验2】密码非空
if password == "" {
return errors.New("密码不能为空")
}
// 【前置校验3】禁止重复登录:检查用户是否已在在线列表中
if isUserOnline(username) {
return errors.New("该用户已在线,禁止重复登录")
}
// 命令分发
switch cmd {
case "register":
return handleRegister(user, username, password)
case "login":
return handleLogin(user, username, password)
default:
return errors.New("未知命令,仅支持register/login")
}
}
- 核心语法:
strings.SplitN的第三个参数3是关键,确保密码中包含|时不会被分割,避免命令解析异常 - 开发规范:所有前置校验必须在数据库操作前完成,减少无效的数据库查询,提升性能的同时避免数据库压力
注册逻辑实现
go
func handleRegister(user *User, username, password string) error {
// 1. 检查用户名是否已被注册
_, err := GetUserByName(username)
if err == nil {
return errors.New("用户名已存在")
}
// 2. 创建用户(DAO层自动完成密码哈希)
dbUser, err := CreateUser(username, password)
if err != nil {
return errors.New("注册失败,请稍后重试")
}
// 3. 注册成功:设置用户状态,标记在线
user.Name = dbUser.Username
user.Authenticated = true
user.Online()
_ = UpdateUserStatus(username, true)
return nil
}
登录逻辑实现
go
func handleLogin(user *User, username, password string) error {
// 1. 查询用户是否存在
dbUser, err := GetUserByName(username)
if err != nil {
return errors.New("用户名或密码错误")
}
// 2. 强制校验密码哈希(核心安全步骤)
err = bcrypt.CompareHashAndPassword([]byte(dbUser.PasswordHash), []byte(password))
if err != nil {
return errors.New("用户名或密码错误")
}
// 3. 登录成功:设置用户状态,标记在线
user.Name = dbUser.Username
user.Authenticated = true
user.Online()
_ = UpdateUserStatus(username, true)
return nil
}
- 安全规范:无论用户名不存在还是密码错误,都返回统一的错误提示,避免攻击者枚举用户名
- 状态同步:登录 / 注册成功后,必须同时更新内存状态和数据库状态,保证数据一致性
业务命令处理
认证通过后,所有客户端数据都会进入这个函数,处理正常的业务协议(如 JSON 格式的业务命令)。
go
func processBusinessCommand(user *User, rawLine string) {
// 示例:解析JSON协议、处理业务逻辑、消息转发等
// var msg BusinessMessage
// json.Unmarshal([]byte(rawLine), &msg)
// 业务逻辑处理...
}
安全与性能最佳实践
安全加固要点
- 暴力破解防护:给单 IP / 单用户添加登录失败计数,1 分钟内失败 5 次则临时封禁,避免暴力破解
- TLS 加密传输:生产环境必须使用 TLS 加密 TCP 连接,避免明文传输的账号密码被抓包窃取
- 最小权限原则:未认证连接仅开放注册 / 登录两个命令,其他所有命令全部拦截
- 输入长度限制:限制用户名、密码的最大长度,避免超长输入导致的资源占用
性能优化要点
- 在线用户内存管理 :用
sync.Map存储全局在线用户列表,避免锁竞争,提升重复登录校验的性能 - 数据库操作优化:给用户名字段加唯一索引,减少查询耗时;数据库操作使用异步写入,不阻塞 TCP 主循环
- 连接超时控制:给未认证的连接设置超时时间,30 秒内未完成认证则主动断开,避免空闲连接占用资源
总结
这套 TCP 认证体系的核心,是 「状态隔离 + 分层设计 + 安全优先」 三大原则:
- 用
Authenticated标记实现认证前 / 认证后的严格隔离,从根源上避免未授权访问 - 用 DAO 层封装数据库操作,连接层、认证层、数据层职责清晰,便于维护与扩展
- 用 bcrypt 实现密码安全,配合全流程的输入校验、重复登录防护,构建完整的安全体系