From c38035e25e761d61e12e362698be982e572cf474 Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 5 Sep 2023 11:47:03 +0800 Subject: [PATCH] fix: replace session handler with jwt authorization --- api/core/app_server.go | 118 ++++++++++++--------------- api/core/config.go | 8 -- api/core/types/config.go | 14 ---- api/core/types/session.go | 16 +++- api/handler/admin/admin_handler.go | 43 ++++++---- api/handler/base_handler.go | 38 ++------- api/handler/user_handler.go | 48 +++++++---- api/main.go | 5 +- api/utils/common.go | 27 +++++- api/utils/resp/response.go | 8 +- api/utils/user.go | 19 +---- web/.env.development | 3 +- web/.env.production | 5 +- web/src/store/chat.js | 2 +- web/src/store/session.js | 39 +++++++-- web/src/store/system.js | 2 +- web/src/utils/http.js | 4 +- web/src/views/ChatPlus.vue | 13 ++- web/src/views/Login.vue | 4 +- web/src/views/admin/Login.vue | 4 +- web/src/views/mobile/ChatSession.vue | 2 +- 21 files changed, 217 insertions(+), 205 deletions(-) diff --git a/api/core/app_server.go b/api/core/app_server.go index fd25ed00..ada40913 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -7,16 +7,16 @@ import ( "chatplus/utils" "chatplus/utils/resp" "context" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - "github.com/gin-contrib/sessions/memstore" - "github.com/gin-contrib/sessions/redis" + "fmt" "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + "github.com/golang-jwt/jwt/v5" "gorm.io/gorm" "io" "net/http" "runtime/debug" "strings" + "time" ) type AppServer struct { @@ -53,14 +53,13 @@ func NewServer(appConfig *types.AppConfig, functions map[string]function.Functio } } -func (s *AppServer) Init(debug bool) { +func (s *AppServer) Init(debug bool, client *redis.Client) { if debug { // 调试模式允许跨域请求 API s.Debug = debug logger.Info("Enabled debug mode") } s.Engine.Use(corsMiddleware()) - s.Engine.Use(sessionMiddleware(s.Config)) - s.Engine.Use(authorizeMiddleware(s)) + s.Engine.Use(authorizeMiddleware(s, client)) s.Engine.Use(errorHandler) // 添加静态资源访问 s.Engine.Static("/static", s.Config.StaticDir) @@ -105,42 +104,6 @@ func errorHandler(c *gin.Context) { c.Next() } -// 会话处理 -func sessionMiddleware(config *types.AppConfig) gin.HandlerFunc { - // encrypt the cookie - var store sessions.Store - var err error - switch config.Session.Driver { - case types.SessionDriverMem: - store = memstore.NewStore([]byte(config.Session.SecretKey)) - break - case types.SessionDriverRedis: - store, err = redis.NewStore(10, "tcp", config.Redis.Url(), config.Redis.Password, []byte(config.Session.SecretKey)) - if err != nil { - logger.Fatal(err) - } - break - case types.SessionDriverCookie: - store = cookie.NewStore([]byte(config.Session.SecretKey)) - break - default: - config.Session.Driver = types.SessionDriverCookie - store = cookie.NewStore([]byte(config.Session.SecretKey)) - } - - logger.Info("Session driver: ", config.Session.Driver) - - store.Options(sessions.Options{ - Path: config.Session.Path, - Domain: config.Session.Domain, - MaxAge: config.Session.MaxAge, - Secure: config.Session.Secure, - HttpOnly: config.Session.HttpOnly, - SameSite: config.Session.SameSite, - }) - return sessions.Sessions(config.Session.Name, store) -} - // 跨域中间件设置 func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { @@ -151,7 +114,7 @@ func corsMiddleware() gin.HandlerFunc { c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") //允许跨域设置可以返回其他子段,可以自定义字段 - c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, Chat-Token") + c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, Chat-Token, Admin-Authorization") // 允许浏览器(客户端)可以解析的头部 (重要) c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") //设置缓存时间 @@ -175,7 +138,7 @@ func corsMiddleware() gin.HandlerFunc { } // 用户授权验证 -func authorizeMiddleware(s *AppServer) gin.HandlerFunc { +func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc { return func(c *gin.Context) { if c.Request.URL.Path == "/api/user/login" || c.Request.URL.Path == "/api/admin/login" || @@ -190,29 +153,54 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc { return } - // WebSocket 连接请求验证 - if c.Request.URL.Path == "/api/chat" { - sessionId := c.Query("sessionId") - session := s.ChatSession.Get(sessionId) - if session.ClientIP == c.ClientIP() { - c.Next() - } else { - c.Abort() - } + var tokenString string + if strings.Contains(c.Request.URL.Path, "/api/admin/") { // 后台管理 API + tokenString = c.GetHeader(types.AdminAuthHeader) + } else if c.Request.URL.Path == "/api/chat/new" { + tokenString = c.Query("token") + } else { + tokenString = c.GetHeader(types.UserAuthHeader) + } + if tokenString == "" { + resp.ERROR(c, "You should put Authorization in request headers") + c.Abort() return } - session := sessions.Default(c) - var value interface{} - if strings.Contains(c.Request.URL.Path, "/api/admin/") { // 后台管理 API - value = session.Get(types.SessionAdmin) - } else { - value = session.Get(types.SessionUser) - } - if value != nil { - c.Next() - } else { - resp.NotAuth(c) + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + + return []byte(s.Config.Session.SecretKey), nil + }) + + if err != nil { + resp.NotAuth(c, fmt.Sprintf("Error with parse auth token: %v", err)) c.Abort() + return } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok || !token.Valid { + resp.NotAuth(c, "Token is invalid") + c.Abort() + return + } + + expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0) + if expr > 0 && int64(expr) < time.Now().Unix() { + resp.NotAuth(c, "Token is expired") + c.Abort() + return + } + + key := fmt.Sprintf("users/%v", claims["user_id"]) + if _, err := client.Get(context.Background(), key).Result(); err != nil { + resp.NotAuth(c, "Token is not found in redis") + c.Abort() + return + } + c.Set(types.LoginUserID, claims["user_id"]) } } diff --git a/api/core/config.go b/api/core/config.go index bf387d30..a6a5d956 100644 --- a/api/core/config.go +++ b/api/core/config.go @@ -5,7 +5,6 @@ import ( "chatplus/core/types" logger2 "chatplus/logger" "chatplus/utils" - "net/http" "os" "github.com/BurntSushi/toml" @@ -23,15 +22,8 @@ func NewDefaultConfig() *types.AppConfig { Redis: types.RedisConfig{Host: "localhost", Port: 6379, Password: ""}, AesEncryptKey: utils.RandString(24), Session: types.Session{ - Driver: types.SessionDriverCookie, SecretKey: utils.RandString(64), - Name: "CHAT_PLUS_SESSION", - Domain: "", - Path: "/", MaxAge: 86400, - Secure: true, - HttpOnly: false, - SameSite: http.SameSiteLaxMode, }, ApiConfig: types.ChatPlusApiConfig{}, ExtConfig: types.ChatPlusExtConfig{Token: utils.RandString(32)}, diff --git a/api/core/types/config.go b/api/core/types/config.go index 7045fa22..b6120064 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -2,7 +2,6 @@ package types import ( "fmt" - "net/http" ) type AppConfig struct { @@ -85,19 +84,6 @@ const ( SessionDriverCookie = SessionDriver("cookie") ) -// Session configs struct -type Session struct { - Driver SessionDriver // session 存储驱动 mem|cookie|redis - SecretKey string // session encryption key - Name string - Path string - Domain string - MaxAge int - Secure bool - HttpOnly bool - SameSite http.SameSite -} - // ChatConfig 系统默认的聊天配置 type ChatConfig struct { OpenAI ModelAPIConfig `json:"open_ai"` diff --git a/api/core/types/session.go b/api/core/types/session.go index 5d66bdb6..ab95de56 100644 --- a/api/core/types/session.go +++ b/api/core/types/session.go @@ -1,6 +1,14 @@ package types -const SessionName = "ChatGPT-TOKEN" -const SessionUser = "SESSION_USER" // 存储用户信息的 session key -const SessionAdmin = "SESSION_ADMIN" //存储管理员信息的 session key -const LoginUserCache = "LOGIN_USER_CACHE" // 已登录用户缓存 +const LoginUserID = "LOGIN_USER_ID" +const LoginUserCache = "LOGIN_USER_CACHE" + +const UserAuthHeader = "Authorization" +const AdminAuthHeader = "Admin-Authorization" +const ChatTokenHeader = "Chat-Token" + +// Session configs struct +type Session struct { + SecretKey string + MaxAge int +} diff --git a/api/handler/admin/admin_handler.go b/api/handler/admin/admin_handler.go index 8777ff80..4c122ef4 100644 --- a/api/handler/admin/admin_handler.go +++ b/api/handler/admin/admin_handler.go @@ -8,9 +8,11 @@ import ( "chatplus/store/model" "chatplus/utils" "chatplus/utils/resp" + "context" + "github.com/go-redis/redis/v8" + "github.com/golang-jwt/jwt/v5" "strings" - - "github.com/gin-contrib/sessions" + "time" "github.com/gin-gonic/gin" "gorm.io/gorm" @@ -20,11 +22,12 @@ var logger = logger2.GetLogger() type ManagerHandler struct { handler.BaseHandler - db *gorm.DB + db *gorm.DB + redis *redis.Client } -func NewAdminHandler(app *core.AppServer, db *gorm.DB) *ManagerHandler { - h := ManagerHandler{db: db} +func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client) *ManagerHandler { + h := ManagerHandler{db: db, redis: client} h.App = app return &h } @@ -38,13 +41,22 @@ func (h *ManagerHandler) Login(c *gin.Context) { } manager := h.App.Config.Manager if data.Username == manager.Username && data.Password == manager.Password { - err := utils.SetLoginAdmin(c, manager) + // 创建 token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "user_id": manager.Username, + "expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)), + }) + tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey)) if err != nil { - resp.ERROR(c, "Save session failed") + resp.ERROR(c, "Failed to generate token, "+err.Error()) return } - manager.Password = "" // 清空密码] - resp.SUCCESS(c, manager) + // 保存到 redis + if _, err := h.redis.Set(context.Background(), "users/"+manager.Username, tokenString, 0).Result(); err != nil { + resp.ERROR(c, "error with save token: "+err.Error()) + return + } + resp.SUCCESS(c, tokenString) } else { resp.ERROR(c, "用户名或者密码错误") } @@ -52,11 +64,9 @@ func (h *ManagerHandler) Login(c *gin.Context) { // Logout 注销 func (h *ManagerHandler) Logout(c *gin.Context) { - session := sessions.Default(c) - session.Delete(types.SessionAdmin) - err := session.Save() - if err != nil { - resp.ERROR(c, "Save session failed") + token := c.GetHeader(types.AdminAuthHeader) + if _, err := h.redis.Del(c, token).Result(); err != nil { + logger.Error("error with delete session: ", err) } else { resp.SUCCESS(c) } @@ -64,9 +74,8 @@ func (h *ManagerHandler) Logout(c *gin.Context) { // Session 会话检测 func (h *ManagerHandler) Session(c *gin.Context) { - session := sessions.Default(c) - admin := session.Get(types.SessionAdmin) - if admin == nil { + token := c.GetHeader(types.AdminAuthHeader) + if token == "" { resp.NotAuth(c) } else { resp.SUCCESS(c) diff --git a/api/handler/base_handler.go b/api/handler/base_handler.go index 303163bf..8c60d0ae 100644 --- a/api/handler/base_handler.go +++ b/api/handler/base_handler.go @@ -3,7 +3,7 @@ package handler import ( "chatplus/core" logger2 "chatplus/logger" - "strconv" + "chatplus/utils" "strings" "github.com/gin-gonic/gin" @@ -20,47 +20,23 @@ func (h *BaseHandler) GetTrim(c *gin.Context, key string) string { } func (h *BaseHandler) PostInt(c *gin.Context, key string, defaultValue int) int { - return intValue(c.PostForm(key), defaultValue) + return utils.IntValue(c.PostForm(key), defaultValue) } func (h *BaseHandler) GetInt(c *gin.Context, key string, defaultValue int) int { - return intValue(c.Query(key), defaultValue) -} - -func intValue(str string, defaultValue int) int { - value, err := strconv.Atoi(str) - if err != nil { - return defaultValue - } - return value + return utils.IntValue(c.Query(key), defaultValue) } func (h *BaseHandler) GetFloat(c *gin.Context, key string) float64 { - return floatValue(c.Query(key)) + return utils.FloatValue(c.Query(key)) } func (h *BaseHandler) PostFloat(c *gin.Context, key string) float64 { - return floatValue(c.PostForm(key)) -} - -func floatValue(str string) float64 { - value, err := strconv.ParseFloat(str, 64) - if err != nil { - return 0 - } - return value + return utils.FloatValue(c.PostForm(key)) } func (h *BaseHandler) GetBool(c *gin.Context, key string) bool { - return boolValue(c.Query(key)) + return utils.BoolValue(c.Query(key)) } func (h *BaseHandler) PostBool(c *gin.Context, key string) bool { - return boolValue(c.PostForm(key)) -} - -func boolValue(str string) bool { - value, err := strconv.ParseBool(str) - if err != nil { - return false - } - return value + return utils.BoolValue(c.PostForm(key)) } diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 2ce4baf1..014dfe76 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -9,10 +9,12 @@ import ( "chatplus/store/vo" "chatplus/utils" "chatplus/utils/resp" + "fmt" + "github.com/go-redis/redis/v8" + "github.com/golang-jwt/jwt/v5" "strings" "time" - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/lionsoul2014/ip2region/binding/golang/xdb" "gorm.io/gorm" @@ -23,6 +25,7 @@ type UserHandler struct { db *gorm.DB searcher *xdb.Searcher leveldb *store.LevelDB + redis *redis.Client uploadManager *oss.UploaderManager } @@ -31,8 +34,9 @@ func NewUserHandler( db *gorm.DB, searcher *xdb.Searcher, levelDB *store.LevelDB, + client *redis.Client, manager *oss.UploaderManager) *UserHandler { - handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB, uploadManager: manager} + handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB, redis: client, uploadManager: manager} handler.App = app return handler } @@ -122,7 +126,7 @@ func (h *UserHandler) Register(c *gin.Context) { // Login 用户登录 func (h *UserHandler) Login(c *gin.Context) { var data struct { - Username string `json:"mobile"` + Mobile string `json:"username"` Password string `json:"password"` } if err := c.ShouldBindJSON(&data); err != nil { @@ -130,7 +134,7 @@ func (h *UserHandler) Login(c *gin.Context) { return } var user model.User - res := h.db.Where("mobile = ?", data.Username).First(&user) + res := h.db.Where("mobile = ?", data.Mobile).First(&user) if res.Error != nil { resp.ERROR(c, "用户名不存在") return @@ -152,13 +156,6 @@ func (h *UserHandler) Login(c *gin.Context) { user.LastLoginAt = time.Now().Unix() h.db.Model(&user).Updates(user) - err := utils.SetLoginUser(c, user) - if err != nil { - resp.ERROR(c, "保存会话失败") - logger.Error("Error for save session: ", err) - return - } - h.db.Create(&model.UserLoginLog{ UserId: user.Id, Username: user.Mobile, @@ -166,17 +163,32 @@ func (h *UserHandler) Login(c *gin.Context) { LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()), }) - resp.SUCCESS(c) + // 创建 token + expired := time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "user_id": user.Id, + "expired": expired, + }) + tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey)) + if err != nil { + resp.ERROR(c, "Failed to generate token, "+err.Error()) + return + } + // 保存到 redis + key := fmt.Sprintf("users/%d", user.Id) + if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil { + resp.ERROR(c, "error with save token: "+err.Error()) + return + } + resp.SUCCESS(c, tokenString) } // Logout 注 销 func (h *UserHandler) Logout(c *gin.Context) { - sessionId := c.GetHeader(types.SessionName) - session := sessions.Default(c) - session.Delete(types.SessionUser) - err := session.Save() - if err != nil { - logger.Error("Error for save session: ", err) + sessionId := c.GetHeader(types.ChatTokenHeader) + token := c.GetHeader(types.UserAuthHeader) + if _, err := h.redis.Del(c, token).Result(); err != nil { + logger.Error("error with delete session: ", err) } // 删除 websocket 会话列表 h.App.ChatSession.Delete(sessionId) diff --git a/api/main.go b/api/main.go index c992b77b..2faae0c7 100644 --- a/api/main.go +++ b/api/main.go @@ -12,6 +12,7 @@ import ( "chatplus/store" "context" "embed" + "github.com/go-redis/redis/v8" "io" "log" "os" @@ -81,8 +82,8 @@ func main() { // 创建应用服务 fx.Provide(core.NewServer), // 初始化 - fx.Invoke(func(s *core.AppServer) { - s.Init(debug) + fx.Invoke(func(s *core.AppServer, client *redis.Client) { + s.Init(debug, client) }), // 初始化数据库 diff --git a/api/utils/common.go b/api/utils/common.go index a88c286d..c1d7c64a 100644 --- a/api/utils/common.go +++ b/api/utils/common.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "reflect" + "strconv" "strings" "github.com/lionsoul2014/ip2region/binding/golang/xdb" @@ -92,7 +93,7 @@ func IsEmptyValue(obj interface{}) bool { if obj == nil { return true } - + v := reflect.ValueOf(obj) switch v.Kind() { case reflect.Ptr, reflect.Interface: @@ -113,3 +114,27 @@ func IsEmptyValue(obj interface{}) bool { return reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface()) } } + +func BoolValue(str string) bool { + value, err := strconv.ParseBool(str) + if err != nil { + return false + } + return value +} + +func FloatValue(str string) float64 { + value, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0 + } + return value +} + +func IntValue(str string, defaultValue int) int { + value, err := strconv.Atoi(str) + if err != nil { + return defaultValue + } + return value +} diff --git a/api/utils/resp/response.go b/api/utils/resp/response.go index 68e593aa..0406c722 100644 --- a/api/utils/resp/response.go +++ b/api/utils/resp/response.go @@ -27,6 +27,10 @@ func HACKER(c *gin.Context) { c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"}) } -func NotAuth(c *gin.Context) { - c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"}) +func NotAuth(c *gin.Context, messages ...string) { + if messages != nil { + c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: messages[0]}) + } else { + c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"}) + } } diff --git a/api/utils/user.go b/api/utils/user.go index 41cf3e0b..497db8c3 100644 --- a/api/utils/user.go +++ b/api/utils/user.go @@ -5,33 +5,18 @@ import ( "chatplus/store/model" "errors" - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "gorm.io/gorm" ) -func SetLoginUser(c *gin.Context, user model.User) error { - session := sessions.Default(c) - session.Set(types.SessionUser, user.Id) - // TODO: 后期用户数量增加,考虑将用户数据存储到 leveldb,避免每次查询数据库 - return session.Save() -} - -func SetLoginAdmin(c *gin.Context, admin types.Manager) error { - session := sessions.Default(c) - session.Set(types.SessionAdmin, admin.Username) - return session.Save() -} - func GetLoginUser(c *gin.Context, db *gorm.DB) (model.User, error) { value, exists := c.Get(types.LoginUserCache) if exists { return value.(model.User), nil } - session := sessions.Default(c) - userId := session.Get(types.SessionUser) - if userId == nil { + userId, ok := c.Get(types.LoginUserID) + if !ok { return model.User{}, errors.New("user not login") } diff --git a/web/.env.development b/web/.env.development index a5d967bd..430349e1 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,7 +1,8 @@ VUE_APP_API_HOST=http://localhost:5678 VUE_APP_WS_HOST=ws://localhost:5678 -VUE_APP_USER=geekmaster +VUE_APP_USER=18575670125 VUE_APP_PASS=12345678 VUE_APP_ADMIN_USER=admin VUE_APP_ADMIN_PASS=admin123 +VUE_APP_KEY_PREFIX=ChatPLUS_ VUE_APP_TITLE="ChatGPT-PLUS V3" diff --git a/web/.env.production b/web/.env.production index 5481a75e..7b9c8cdf 100644 --- a/web/.env.production +++ b/web/.env.production @@ -1,7 +1,4 @@ VUE_APP_API_HOST= VUE_APP_WS_HOST= -VUE_APP_USER= -VUE_APP_PASS= -VUE_APP_ADMIN_USER= -VUE_APP_ADMIN_PASS= +VUE_APP_KEY_PREFIX=ChatPLUS_ VUE_APP_TITLE="ChatGPT-PLUS V3" diff --git a/web/src/store/chat.js b/web/src/store/chat.js index 09fa0d18..40d12c1f 100644 --- a/web/src/store/chat.js +++ b/web/src/store/chat.js @@ -1,6 +1,6 @@ import Storage from 'good-storage' -const CHAT_CONFIG_KEY = "chat_config" +const CHAT_CONFIG_KEY = process.env.VUE_APP_KEY_PREFIX + "chat_config" export function getChatConfig() { return Storage.get(CHAT_CONFIG_KEY) diff --git a/web/src/store/session.js b/web/src/store/session.js index 906b3566..fa571082 100644 --- a/web/src/store/session.js +++ b/web/src/store/session.js @@ -1,15 +1,16 @@ -/* eslint-disable no-constant-condition */ - import {randString} from "@/utils/libs"; +import Storage from "good-storage"; /** * storage handler */ -const SessionIDKey = 'SESSION_ID'; +const SessionIDKey = process.env.VUE_APP_KEY_PREFIX + 'SESSION_ID'; +const UserTokenKey = process.env.VUE_APP_KEY_PREFIX + "Authorization"; +const AdminTokenKey = process.env.VUE_APP_KEY_PREFIX + "Admin-Authorization" export function getSessionId() { - let sessionId = sessionStorage.getItem(SessionIDKey) + let sessionId = Storage.get(SessionIDKey) if (!sessionId) { sessionId = randString(42) setSessionId(sessionId) @@ -17,10 +18,34 @@ export function getSessionId() { return sessionId } -export function removeLoginUser() { - sessionStorage.removeItem(SessionIDKey) +export function removeSessionId() { + Storage.remove(SessionIDKey) } export function setSessionId(sessionId) { - sessionStorage.setItem(SessionIDKey, sessionId) + Storage.set(SessionIDKey, sessionId) +} + +export function getUserToken() { + return Storage.get(UserTokenKey) +} + +export function setUserToken(token) { + Storage.set(UserTokenKey, token) +} + +export function removeUserToken() { + Storage.remove(UserTokenKey) +} + +export function getAdminToken() { + return Storage.get(AdminTokenKey) +} + +export function setAdminToken(token) { + Storage.set(AdminTokenKey, token) +} + +export function removeAdminToken() { + Storage.remove(AdminTokenKey) } diff --git a/web/src/store/system.js b/web/src/store/system.js index 794e4851..02ac51e5 100644 --- a/web/src/store/system.js +++ b/web/src/store/system.js @@ -1,6 +1,6 @@ import Storage from "good-storage"; -const MOBILE_THEME = "MOBILE_THEME" +const MOBILE_THEME = process.env.VUE_APP_KEY_PREFIX + "MOBILE_THEME" export function getMobileTheme() { return Storage.get(MOBILE_THEME) ? Storage.get(MOBILE_THEME) : 'light' diff --git a/web/src/utils/http.js b/web/src/utils/http.js index 7500b080..205fd0b5 100644 --- a/web/src/utils/http.js +++ b/web/src/utils/http.js @@ -1,5 +1,5 @@ import axios from 'axios' -import {getSessionId} from "@/store/session"; +import {getAdminToken, getSessionId, getUserToken} from "@/store/session"; axios.defaults.timeout = 10000 axios.defaults.baseURL = process.env.VUE_APP_API_HOST @@ -11,6 +11,8 @@ axios.interceptors.request.use( config => { // set token config.headers['Chat-Token'] = getSessionId(); + config.headers['Authorization'] = getUserToken(); + config.headers['Admin-Authorization'] = getAdminToken(); return config }, error => { return Promise.reject(error) diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 06148369..77f72325 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -271,7 +271,7 @@ import 'highlight.js/styles/a11y-dark.css' import {dateFormat, isMobile, randString, removeArrayItem, renderInputText, UUID} from "@/utils/libs"; import {ElMessage, ElMessageBox} from "element-plus"; import hl from "highlight.js"; -import {getSessionId, removeLoginUser} from "@/store/session"; +import {getSessionId, getUserToken, removeUserToken} from "@/store/session"; import {httpGet, httpPost} from "@/utils/http"; import {useRouter} from "vue-router"; import Clipboard from "clipboard"; @@ -319,9 +319,6 @@ onMounted(() => { checkSession().then((user) => { loginUser.value = user isLogin.value = true - if (user.chat_config?.model !== '') { - modelID.value = user.chat_config.model - } // 加载角色列表 httpGet(`/api/role/list?user_id=${user.id}`).then((res) => { roles.value = res.data; @@ -400,7 +397,7 @@ const newChat = function () { chat_id: "", icon: icon, role_id: roleId.value, - model: modelID.value, + model_id: modelID.value, title: '', edit: false, removing: false, @@ -419,7 +416,7 @@ const changeChat = function (chat) { activeChat.value = chat newChatItem.value = null; roleId.value = chat.role_id; - modelID.value = chat.model; + modelID.value = chat.model_id; showStopGenerate.value = false; showReGenerate.value = false; connect(chat.chat_id, chat.role_id) @@ -510,7 +507,7 @@ const connect = function (chat_id, role_id) { host = 'ws://' + location.host; } } - const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}`); + const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`); _socket.addEventListener('open', () => { chatData.value = []; // 初始化聊天数据 previousText.value = ''; @@ -740,7 +737,7 @@ const clearAllChats = function () { const logout = function () { activelyClose.value = true; httpGet('/api/user/logout').then(() => { - removeLoginUser(); + removeUserToken(); router.push('login'); }).catch(() => { ElMessage.error('注销失败!'); diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 733cd8f9..e696d300 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -56,6 +56,7 @@ import {useRouter} from "vue-router"; import FooterBar from "@/components/FooterBar.vue"; import {isMobile} from "@/utils/libs"; import {checkSession} from "@/action/session"; +import {setUserToken} from "@/store/session"; const router = useRouter(); const title = ref('ChatGPT-PLUS 用户登录'); @@ -87,7 +88,8 @@ const login = function () { return ElMessage.error('请输入密码'); } - httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then(() => { + httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => { + setUserToken(res.data) if (isMobile()) { router.push('/mobile') } else { diff --git a/web/src/views/admin/Login.vue b/web/src/views/admin/Login.vue index 7909861c..6c107fdb 100644 --- a/web/src/views/admin/Login.vue +++ b/web/src/views/admin/Login.vue @@ -46,6 +46,7 @@ import {httpPost} from "@/utils/http"; import {ElMessage} from "element-plus"; import {useRouter} from "vue-router"; import FooterBar from "@/components/FooterBar.vue"; +import {setAdminToken} from "@/store/session"; const router = useRouter(); const title = ref('ChatGPT Plus Admin'); @@ -68,7 +69,8 @@ const login = function () { return ElMessage.error('请输入密码'); } - httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => { + httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then(res => { + setAdminToken(res.data) router.push("/admin") }).catch((e) => { ElMessage.error('登录失败,' + e.message) diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index f0bdd8d9..5f795885 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -200,7 +200,7 @@ const connect = function (chat_id, role_id) { host = 'ws://' + location.host; } } - const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model}`); + const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model}&token=${getUserToken()}`); _socket.addEventListener('open', () => { loading.value = false previousText.value = '';