package auth import ( "context" "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "strings" ) const ( sessionTokenPrefix = "nst_" apiTokenPrefix = "nxm_" tokenRandomBytes = 32 ) // SessionTokenService 生成与哈希端会话令牌。 type SessionTokenService struct{} // NewSessionTokens 创建会话令牌服务。 func NewSessionTokens() *SessionTokenService { return &SessionTokenService{} } // Issue 生成 nst_ + 32 字节随机数的 base64url;返回明文与 SHA-256。 func (s *SessionTokenService) Issue(_ context.Context) (string, []byte, error) { return issuePrefixedToken(sessionTokenPrefix) } // HashToken 对令牌做 SHA-256。 func (s *SessionTokenService) HashToken(token string) []byte { sum := sha256.Sum256([]byte(token)) return sum[:] } // LooksLikeSessionToken 判断是否以 nst_ 开头。 func (s *SessionTokenService) LooksLikeSessionToken(credential string) bool { return strings.HasPrefix(credential, sessionTokenPrefix) } // APITokenService 生成与哈希 API 令牌。 type APITokenService struct{} // NewAPITokens 创建 API 令牌服务。 func NewAPITokens() *APITokenService { return &APITokenService{} } // Issue 生成 nxm_ + 32 字节随机数的 base64url;返回明文与 SHA-256。 func (s *APITokenService) Issue(_ context.Context) (string, []byte, error) { return issuePrefixedToken(apiTokenPrefix) } // HashToken 对令牌做 SHA-256。 func (s *APITokenService) HashToken(token string) []byte { sum := sha256.Sum256([]byte(token)) return sum[:] } // LooksLikeAPIToken 判断是否以 nxm_ 开头。 func (s *APITokenService) LooksLikeAPIToken(credential string) bool { return strings.HasPrefix(credential, apiTokenPrefix) } // EqualHash 常量时间比较两个哈希。 func EqualHash(a, b []byte) bool { if len(a) != len(b) { return false } return subtle.ConstantTimeCompare(a, b) == 1 } func issuePrefixedToken(prefix string) (string, []byte, error) { raw := make([]byte, tokenRandomBytes) if _, err := rand.Read(raw); err != nil { return "", nil, err } tok := prefix + base64.RawURLEncoding.EncodeToString(raw) sum := sha256.Sum256([]byte(tok)) return tok, sum[:], nil } var ( _ SessionTokens = (*SessionTokenService)(nil) _ APITokens = (*APITokenService)(nil) )