合并
This commit is contained in:
parent
01cc7da3b4
commit
19ad6625e6
|
@ -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))
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
146
utils/auth/confirmation_link.go
Normal file
146
utils/auth/confirmation_link.go
Normal 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
|
||||
}
|
80
utils/auth/confirmation_link_test.go
Normal file
80
utils/auth/confirmation_link_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"))
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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请求体中解析请求数据
|
||||
|
|
Loading…
Reference in New Issue
Block a user