feat(backend): bcrypt+jwt auth package and admin seeding

This commit is contained in:
2026-09-04 23:50:52 +08:00
parent c1761d0f3e
commit b224288dae
6 changed files with 168 additions and 1 deletions
+54
View File
@@ -0,0 +1,54 @@
package auth
import (
"crypto/subtle"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
var ErrToken = errors.New("invalid token")
func HashPassword(plain string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(plain), 12)
return string(b), err
}
func CheckPassword(hash, plain string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(plain)) == nil
}
type Claims struct {
UID int64 `json:"uid"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func Sign(secret []byte, uid int64, role string) (string, error) {
return SignWithTTL(secret, uid, role, 72*time.Hour)
}
func SignWithTTL(secret []byte, uid int64, role string, ttl time.Duration) (string, error) {
t := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{
UID: uid, Role: role,
RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl))},
})
return t.SignedString(secret)
}
func Parse(secret []byte, token string) (*Claims, error) {
c := &Claims{}
parsed, err := jwt.ParseWithClaims(token, c, func(t *jwt.Token) (any, error) {
m, ok := t.Method.(*jwt.SigningMethodHMAC)
if !ok || subtle.ConstantTimeCompare([]byte(m.Alg()), []byte("HS256")) != 1 {
return nil, ErrToken
}
return secret, nil
}, jwt.WithValidMethods([]string{"HS256"}))
if err != nil || !parsed.Valid {
return nil, ErrToken
}
return c, nil
}
+40
View File
@@ -0,0 +1,40 @@
package auth
import (
"strings"
"testing"
"time"
)
func TestHashCheck(t *testing.T) {
h, err := HashPassword("hunter2")
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(h, "$2a$") || !CheckPassword(h, "hunter2") || CheckPassword(h, "wrong") {
t.Fatal("bcrypt wrong")
}
}
func TestSignParse(t *testing.T) {
secret := []byte("k")
tok, err := Sign(secret, 7, "admin")
if err != nil {
t.Fatal(err)
}
c, err := Parse(secret, tok)
if err != nil || c.UID != 7 || c.Role != "admin" {
t.Fatalf("parse: %v %+v", err, c)
}
if _, err := Parse([]byte("other"), tok); err == nil {
t.Fatal("must reject wrong secret")
}
expired, _ := SignWithTTL(secret, 1, "member", -time.Hour)
if _, err := Parse(secret, expired); err == nil {
t.Fatal("must reject expired")
}
// alg 混淆攻击:none 必须拒
if _, err := Parse(secret, "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1aWQiOjF9."); err == nil {
t.Fatal("must reject alg=none")
}
}