This commit is contained in:
eson 2023-07-27 10:18:49 +08:00
parent 01cc7da3b4
commit 19ad6625e6
12 changed files with 301 additions and 85 deletions

View File

@ -34,36 +34,4 @@ func NewServiceContext(c {{.config}}) *ServiceContext {
AllModels: gmodel.NewAllModels(initalize.InitMysql(c.SourceMysql)),
{{.middlewareAssignment}}
}
}
func (svcCtx *ServiceContext) ParseJwtToken(r *http.Request) (jwt.MapClaims, error) {
AuthKey := r.Header.Get("Authorization")
if AuthKey == "" {
return nil, nil
}
AuthKey = AuthKey[7:]
if len(AuthKey) <= 50 {
return nil, errors.New(fmt.Sprint("Error parsing token, len:", len(AuthKey)))
}
token, err := jwt.Parse(AuthKey, func(token *jwt.Token) (interface{}, error) {
// 检查签名方法是否为 HS256
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// 返回用于验证签名的密钥
return []byte(svcCtx.Config.Auth.AccessSecret), nil
})
if err != nil {
return nil, errors.New(fmt.Sprint("Error parsing token:", err))
}
// 验证成功返回
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New(fmt.Sprint("Invalid token", err))
}

View File

@ -31,7 +31,7 @@ type UserGoogleLoginLogic struct {
isRegistered bool // 是否注册
registerToken string // 注册邮箱的token
oauthinfo *auth.OAuthInfo
registerInfo *auth.RegisterToken
}
func NewUserGoogleLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserGoogleLoginLogic {
@ -52,19 +52,11 @@ func (l *UserGoogleLoginLogic) AfterLogic(w http.ResponseWriter, r *http.Request
if resp.Code == 200 {
if !l.isRegistered {
now := time.Now()
rtoken, err := auth.GenerateRegisterToken(
&l.svcCtx.Config.Auth.AccessSecret,
l.svcCtx.Config.Auth.AccessExpire,
now.Unix(),
l.oauthinfo.Id,
l.oauthinfo.Platform,
)
rtoken, err := l.svcCtx.TokenManger.Encrypt(l.registerInfo)
if err != nil {
resp.SetStatus(basic.CodeOAuthRegisterTokenErr)
}
l.registerToken = rtoken
}
@ -165,7 +157,8 @@ func (l *UserGoogleLoginLogic) UserGoogleLogin(req *types.RequestGoogleLogin, us
// 如果密码匹配,则生成 JWT Token。
nowSec := time.Now().Unix()
jwtToken, err := auth.GenerateJwtToken(&l.svcCtx.Config.Auth.AccessSecret, l.svcCtx.Config.Auth.AccessExpire, nowSec, user.Id, 0)
jwtToken, err := auth.GenerateJwtTokenUint64(auth.StringToHash(*user.PasswordHash), l.svcCtx.Config.Auth.AccessExpire, nowSec, user.Id, 0)
// 如果生成 JWT Token 失败,则抛出错误并返回未认证的状态码。
if err != nil {

View File

@ -68,8 +68,13 @@ func (l *UserLoginLogic) UserLogin(req *types.RequestUserLogin, userinfo *auth.U
// 如果密码匹配,则生成 JWT Token。
nowSec := time.Now().Unix()
us, err := l.svcCtx.SharedState.GetUserState(user.Id)
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeSharedStateErr)
}
jwtToken, err := auth.GenerateJwtToken(&l.svcCtx.Config.Auth.AccessSecret, l.svcCtx.Config.Auth.AccessExpire, nowSec, user.Id, 0)
jwtToken, err := auth.GenerateJwtTokenUint64(us.PwdHash, l.svcCtx.Config.Auth.AccessExpire, nowSec, user.Id, 0)
// 如果生成 JWT Token 失败,则抛出错误并返回未认证的状态码。
if err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"fusenapi/fsm"
"fusenapi/server/auth/internal/config"
"fusenapi/utils/auth"
"fusenapi/utils/autoconfig"
"net/http"
@ -21,6 +22,8 @@ type ServiceContext struct {
MysqlConn *gorm.DB
AllModels *gmodel.AllModelsGen
TokenManger *auth.ConfirmationLink[auth.RegisterToken]
}
func NewServiceContext(c config.Config) *ServiceContext {
@ -32,6 +35,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
MysqlConn: conn,
SharedState: StateServer,
AllModels: gmodel.NewAllModels(initalize.InitMysql(c.SourceMysql)),
TokenManger: auth.NewConfirmationLink[auth.RegisterToken]([]byte(c.Auth.AccessSecret), "http://localhost:9900/api/auth/oauth2/register"),
}
}

View File

@ -15,7 +15,7 @@ func GetCloudListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetCloudListReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
userinfo, err := basic.RequestParse(w, r, svcCtx.SharedState, &req)
if err != nil {
return
}

View File

@ -0,0 +1,146 @@
package auth
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/gob"
"fmt"
"net/url"
)
type ConfirmationLink[T any] struct {
Secret []byte
DefaultQueryKey string // 默认key 是 token
link *url.URL
}
func NewConfirmationLink[T any](key []byte, UrlStr string) *ConfirmationLink[T] {
u, err := url.Parse(UrlStr)
if err != nil {
panic(err)
}
return &ConfirmationLink[T]{
Secret: key,
DefaultQueryKey: "token",
link: u,
}
}
// Generate 序列化链接传入需求的obj
func (cl *ConfirmationLink[T]) Generate(obj *T) (string, error) {
token, err := cl.Encrypt(obj)
if err != nil {
return "", err
}
return cl.GenerateWithToken(token)
}
// GenerateWithToken 序列化url带token
func (cl *ConfirmationLink[T]) GenerateWithToken(token string) (string, error) {
q := cl.link.Query()
if q.Has(cl.DefaultQueryKey) {
q.Set(cl.DefaultQueryKey, token)
} else {
q.Add(cl.DefaultQueryKey, token)
}
// 生成确认链接
cl.link.RawQuery = q.Encode()
return cl.link.String(), nil
}
func fusenMakeKey(keysting string) []byte {
key := []byte(keysting)
var result [32]byte
// If key length is more than 32, truncate it
if len(key) > 32 {
key = key[:32]
}
// If key length is less than 32, replicate it until it reaches 32
for len(key) < 32 {
key = append(key, key...)
}
// Only take the first 32 bytes
key = key[:32]
// Swap the first 16 bytes with the last 16 bytes
copy(result[:], key[16:])
copy(result[16:], key[:16])
return result[:]
}
func (cl *ConfirmationLink[T]) Encrypt(obj *T) (string, error) {
var buf = bytes.NewBuffer(nil)
err := gob.NewEncoder(buf).Encode(obj)
if err != nil {
return "", err
}
block, err := aes.NewCipher(cl.Secret)
if err != nil {
return "", err
}
nonce := make([]byte, 12)
// if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
// return "", err
// }
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
ciphertext := aesgcm.Seal(nonce, nonce, buf.Bytes(), nil)
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
func (cl *ConfirmationLink[T]) Decrypt(ciphertext string) (*T, error) {
block, err := aes.NewCipher(cl.Secret)
if err != nil {
return nil, err
}
ct, err := base64.URLEncoding.DecodeString(ciphertext)
if err != nil {
return nil, err
}
if len(ct) < 12 {
return nil, fmt.Errorf("ciphertext too short")
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plaintext, err := aesgcm.Open(nil, ct[:12], ct[12:], nil)
if err != nil {
return nil, err
}
// 解出golang的结构体
var protected T
var buf = bytes.NewBuffer(plaintext)
err = gob.NewDecoder(buf).Decode(&protected)
if err != nil {
return nil, err
}
return &protected, nil
}

View File

@ -0,0 +1,80 @@
package auth
import (
"log"
"net/url"
"testing"
"time"
"github.com/golang-jwt/jwt"
)
func BenchmarkConfirmationLink(b *testing.B) {
type Register struct {
Id int64
Password string
platform string
Expired time.Time
}
key := "21321321"
cl := NewConfirmationLink[Register](fusenMakeKey(key), "http://localhost:9900/api/auth/oauth2/register")
for i := 0; i < b.N; i++ {
uri, _ := cl.Generate(&Register{Id: 39, Password: "21dsadsad", platform: "google", Expired: time.Now()})
u, _ := url.Parse(uri)
token := u.Query()["token"]
cl.Decrypt(token[0])
}
}
func TestConfirmationLink(t *testing.T) {
type Register struct {
Id int64
Password string
platform string
Expired time.Time
}
key := "21321321"
cl := NewConfirmationLink[Register](fusenMakeKey(key), "http://localhost:9900/api/auth/oauth2/register")
uri, _ := cl.Generate(&Register{Id: 39, Password: "21dsadsad", platform: "google", Expired: time.Now()})
log.Println(uri)
u, _ := url.Parse(uri)
token := u.Query()["token"]
log.Println(cl.Decrypt(token[0]))
}
const secret = "your-256-bit-secret"
func BenchmarkJWT(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
claims := &jwt.StandardClaims{
ExpiresAt: time.Now().Unix() + 1020213021,
Issuer: "test",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString([]byte(secret))
if err != nil {
b.Fatal(err)
}
_, err = jwt.Parse(ss, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte(secret), nil
})
if err != nil {
b.Fatal(err)
}
}
}

View File

@ -7,10 +7,18 @@ import (
"fmt"
"net/http"
"net/mail"
"time"
"github.com/golang-jwt/jwt"
)
type RegisterToken struct {
Id int64
Password string
Platform string
Expired time.Time
}
func ParseJwtTokenUint64SecretByRequest(r *http.Request, AccessSecret uint64) (jwt.MapClaims, error) {
AuthKey := r.Header.Get("Authorization")
if AuthKey == "" {
@ -93,37 +101,6 @@ func StringToHash(s string) uint64 {
return intHash
}
var secret = []byte("your-secret")
// func generateConfirmationLink(id, email, password, name string, platform string) (string, error) {
// // 创建一个新的 JWT并将用户的电子邮件设置为它的主题。
// token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
// "email": email,
// "password": password,
// "id": id,
// "platform": platform,
// "exp": time.Now().Add(24 * time.Hour).Unix(), // Token expires after 24 hours
// })
// // 签署 JWT。
// tokenString, err := token.SignedString(secret)
// if err != nil {
// return "", err
// }
// // 生成确认链接,这个链接包含 JWT。
// link := url.URL{
// Scheme: "http",
// Host: "yourserver.com",
// Path: "/confirm",
// RawQuery: url.Values{
// "token": []string{tokenString},
// }.Encode(),
// }
// return link.String(), nil
// }
// func handleConfirm(w http.ResponseWriter, r *http.Request) {
// // 从请求中获取 JWT。
// tokenString := r.URL.Query().Get("token")

View File

@ -3,6 +3,7 @@ package auth
import (
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
@ -127,6 +128,30 @@ func GetBackendUserInfoFormMapClaims(claims jwt.MapClaims) (*BackendUserInfo, er
return userinfo, nil
}
// GenerateJwtTokenUint64 网站jwt token生成
func GenerateJwtTokenUint64(AccessSecret uint64, accessExpire, nowSec int64, userid int64, guestid int64) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = nowSec + accessExpire
claims["iat"] = nowSec
if userid == 0 && guestid == 0 {
err := errors.New("userid and guestid cannot be 0 at the same time")
logx.Error(err)
return "", err
}
claims["user_id"] = userid
claims["guest_id"] = guestid
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = claims
key := make([]byte, 8)
binary.BigEndian.PutUint64(key, AccessSecret)
return token.SignedString(key)
}
// GenerateJwtToken 网站jwt token生成
func GenerateJwtToken(accessSecret *string, accessExpire, nowSec int64, userid int64, guestid int64) (string, error) {
claims := make(jwt.MapClaims)
@ -189,7 +214,9 @@ func getJwtClaims(AuthKey string, AccessSecret *string) (jwt.MapClaims, error) {
}
func PasswordHash(pwd string) string {
return base64.URLEncoding.EncodeToString(sha256.New().Sum([]byte(pwd)))
h := sha256.New()
h.Write([]byte(pwd))
return base64.URLEncoding.EncodeToString(h.Sum(nil))
}
func CheckValueRange[T comparable](v T, rangevalues ...T) bool {

View File

@ -1,6 +1,9 @@
package auth
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"log"
"testing"
"time"
@ -54,7 +57,18 @@ func TestGenBackendJwt(t *testing.T) {
}
func TestCase1(t *testing.T) {
// log.Println(base64.URLEncoding.EncodeToString(sha256.New().Sum([]byte("fusen_backend_2023"))))
a := sha256.New()
a.Write([]byte("fusen_backend_3021"))
base64.URLEncoding.EncodeToString(a.Sum(nil))
as := fmt.Sprintf("%x", a.Sum(nil))
log.Println(as, len(as), base64.URLEncoding.EncodeToString(a.Sum(nil)))
// b := sha256.New().Sum([]byte("fusen_backend_2022"))
// bs := fmt.Sprintf("%x", b)
// log.Println(bs)
// log.Println(as == bs)
// log.Println(basic.CBCEncrypt("sdfsdfsadsasdasadas", "1232132131232132"))

View File

@ -74,6 +74,8 @@ var (
CodeAesCbcEncryptionErr = &StatusResponse{5106, "encryption data err"} // 加密数据失败
CodeAesCbcDecryptionErr = &StatusResponse{5107, "decryption data err"} // 解密数据失败
CodeSharedStateErr = &StatusResponse{5201, "shared state server err"} // 状态机错误
)
type Response struct {

View File

@ -53,7 +53,7 @@ func NormalAfterLogic(w http.ResponseWriter, r *http.Request, resp *Response) {
func RequestParse(w http.ResponseWriter, r *http.Request, state *fsm.StateCluster, LogicRequest any) (*auth.UserInfo, error) {
token, info, err := auth.ParseJwtTokenHeader[auth.UserInfo](r)
token, info, err := auth.ParseJwtTokenHeader[auth.UserInfo](r) //解析Token头, 和payload信息
if err != nil {
logx.Error(err)
return nil, err
@ -61,15 +61,15 @@ func RequestParse(w http.ResponseWriter, r *http.Request, state *fsm.StateCluste
var secret uint64 = 0
if info.IsUser() {
us, err := state.GetUserState(info.UserId)
us, err := state.GetUserState(info.UserId) //获取缓存的用户状态
if err != nil {
logx.Error(err)
return nil, err
}
secret = us.PwdHash
secret = us.PwdHash // 获取密码的hash做jwt, 便于重置密码的使用
} else if info.IsGuest() {
secret = DefaultJwtSecret
secret = DefaultJwtSecret //获取默认的hash
}
var userinfo *auth.UserInfo
@ -97,10 +97,10 @@ func RequestParse(w http.ResponseWriter, r *http.Request, state *fsm.StateCluste
logx.Info("unauthorized:", err.Error())
return nil, err
}
} else {
// 如果claims为nil,则认为用户身份为白板用户
userinfo = &auth.UserInfo{UserId: 0, GuestId: 0}
}
} else {
// 白板用户
userinfo = &auth.UserInfo{UserId: 0, GuestId: 0}
}
// 如果端点有请求结构体则使用httpx.Parse方法从HTTP请求体中解析请求数据