You've already forked agentic-coding-workflow
feat(user): session token generator with sha256 storage hash
This commit is contained in:
+37
-4
@@ -1,9 +1,11 @@
|
|||||||
// Package user 内的 auth.go 提供与认证相关的纯函数工具。
|
// Package user 内的 auth.go 提供与认证相关的纯函数工具。
|
||||||
//
|
//
|
||||||
// 当前阶段(E2)只覆盖密码哈希:HashPassword 用 argon2id 默认参数(i=1,
|
// 现已覆盖两组:
|
||||||
// m=64MiB, p=4, saltLen=16, keyLen=32)签发哈希;VerifyPassword 做常量时间
|
// - 密码:HashPassword 用 argon2id 默认参数(i=1, m=64MiB, p=4, saltLen=16,
|
||||||
// 比对。后续 E3 步骤会在同文件追加 SessionToken 相关逻辑(生成原文 + sha256
|
// keyLen=32)签发哈希;VerifyPassword 做常量时间比对。
|
||||||
// 摘要),届时这两组工具会与 Service 层组装出完整的登录链路。
|
// - 会话 token:NewSessionToken 生成 24 字节随机原文(base64 RawURL 编码后
|
||||||
|
// 32 字符)并同时返回 sha256 摘要;HashSessionToken 是纯函数,便于服务端
|
||||||
|
// 根据请求里携带的明文 token 反查 DB 中存储的哈希。
|
||||||
//
|
//
|
||||||
// 设计取舍:
|
// 设计取舍:
|
||||||
// - 业务规则在工具层而非 Service 层校验密码长度(minPasswordLen=8),
|
// - 业务规则在工具层而非 Service 层校验密码长度(minPasswordLen=8),
|
||||||
@@ -12,15 +14,27 @@
|
|||||||
// - VerifyPassword 对 argon2id.ErrInvalidHash 显式返回 (false, nil):
|
// - VerifyPassword 对 argon2id.ErrInvalidHash 显式返回 (false, nil):
|
||||||
// 存量数据中如果出现非 argon2id 格式(例如历史明文或其它算法的残留),
|
// 存量数据中如果出现非 argon2id 格式(例如历史明文或其它算法的残留),
|
||||||
// 调用方应当作 “认证失败” 处理而非系统错误,避免登录入口因为脏数据 500。
|
// 调用方应当作 “认证失败” 处理而非系统错误,避免登录入口因为脏数据 500。
|
||||||
|
// - Session token 明文只在登录响应里下发一次,DB 仅保存 sha256 摘要:
|
||||||
|
// 即便 DB 泄漏,攻击者也无法直接拿哈希去发起认证(sha256 不可逆,且服务端
|
||||||
|
// 入口走 “明文 → 哈希反查” 路径);同时 sha256 是固定 32 字节,便于建唯一
|
||||||
|
// 索引、避免变长字段比对的歧义。
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// sessionTokenRawLen 是会话 token 的随机字节长度。
|
||||||
|
// 24 字节 = 192 bit 熵;base64 RawURL 编码后正好 32 字符且无 padding,
|
||||||
|
// 既保证强度又避免 URL 转义问题。
|
||||||
|
const sessionTokenRawLen = 24
|
||||||
|
|
||||||
// minPasswordLen 是创建账号或修改密码时允许的最短长度。
|
// minPasswordLen 是创建账号或修改密码时允许的最短长度。
|
||||||
// 8 字符是最低基线,更复杂的强度策略(字典、复用检查)留待业务侧扩展。
|
// 8 字符是最低基线,更复杂的强度策略(字典、复用检查)留待业务侧扩展。
|
||||||
const minPasswordLen = 8
|
const minPasswordLen = 8
|
||||||
@@ -48,3 +62,22 @@ func VerifyPassword(plain, hash string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return ok, nil
|
return ok, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSessionToken 返回 (明文 token, sha256(token), err)。
|
||||||
|
// 明文只在登录响应里返回一次;DB 只存 hash,并通过 “明文 → sha256 反查”
|
||||||
|
// 校验请求合法性。
|
||||||
|
func NewSessionToken() (token string, hash []byte, err error) {
|
||||||
|
raw := make([]byte, sessionTokenRawLen)
|
||||||
|
if _, err := rand.Read(raw); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("rand: %w", err)
|
||||||
|
}
|
||||||
|
tok := base64.RawURLEncoding.EncodeToString(raw)
|
||||||
|
return tok, HashSessionToken(tok), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashSessionToken 对会话 token 明文做 sha256 摘要,返回 32 字节固定长度。
|
||||||
|
// 纯函数:相同输入恒返回相同输出,便于服务端按请求中的明文反查 DB。
|
||||||
|
func HashSessionToken(token string) []byte {
|
||||||
|
sum := sha256.Sum256([]byte(token))
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,3 +24,20 @@ func TestHashPassword_RejectsTooShort(t *testing.T) {
|
|||||||
_, err := HashPassword("short")
|
_, err := HashPassword("short")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewSessionToken_LengthAndUniqueness(t *testing.T) {
|
||||||
|
t1, h1, err := NewSessionToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.GreaterOrEqual(t, len(t1), 32) // base64 of 24 bytes
|
||||||
|
require.Len(t, h1, 32) // sha256
|
||||||
|
|
||||||
|
t2, h2, _ := NewSessionToken()
|
||||||
|
require.NotEqual(t, t1, t2)
|
||||||
|
require.NotEqual(t, h1, h2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashSessionToken_Deterministic(t *testing.T) {
|
||||||
|
h1 := HashSessionToken("foo")
|
||||||
|
h2 := HashSessionToken("foo")
|
||||||
|
require.Equal(t, h1, h2)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user