mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-19 22:09:03 +08:00
perf: optimize login API logic (#11104)
This commit is contained in:
parent
8ccf1fcbf3
commit
ac43f00273
9 changed files with 143 additions and 28 deletions
|
|
@ -2,6 +2,7 @@ package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
|
@ -29,12 +30,15 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.IgnoreCaptcha {
|
ip := common.GetRealClientIP(c)
|
||||||
|
needCaptcha := global.IPTracker.NeedCaptcha(ip)
|
||||||
|
if needCaptcha {
|
||||||
if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" {
|
if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" {
|
||||||
helper.BadAuth(c, errMsg, nil)
|
helper.BadAuth(c, errMsg, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entranceItem := c.Request.Header.Get("EntranceCode")
|
entranceItem := c.Request.Header.Get("EntranceCode")
|
||||||
var entrance []byte
|
var entrance []byte
|
||||||
if len(entranceItem) != 0 {
|
if len(entranceItem) != 0 {
|
||||||
|
|
@ -50,13 +54,18 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||||
user, msgKey, err := authService.Login(c, req, string(entrance))
|
user, msgKey, err := authService.Login(c, req, string(entrance))
|
||||||
go saveLoginLogs(c, err)
|
go saveLoginLogs(c, err)
|
||||||
if msgKey == "ErrAuth" || msgKey == "ErrEntrance" {
|
if msgKey == "ErrAuth" || msgKey == "ErrEntrance" {
|
||||||
|
if msgKey == "ErrAuth" {
|
||||||
|
global.IPTracker.SetNeedCaptcha(ip)
|
||||||
|
}
|
||||||
helper.BadAuth(c, msgKey, err)
|
helper.BadAuth(c, msgKey, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.IPTracker.SetNeedCaptcha(ip)
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
global.IPTracker.Clear(ip)
|
||||||
helper.SuccessWithData(c, user)
|
helper.SuccessWithData(c, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +151,8 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ip := common.GetRealClientIP(c)
|
||||||
|
needCaptcha := global.IPTracker.NeedCaptcha(ip)
|
||||||
res := &dto.LoginSetting{
|
res := &dto.LoginSetting{
|
||||||
IsDemo: global.CONF.Base.IsDemo,
|
IsDemo: global.CONF.Base.IsDemo,
|
||||||
IsIntl: global.CONF.Base.IsIntl,
|
IsIntl: global.CONF.Base.IsIntl,
|
||||||
|
|
@ -151,6 +162,7 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
|
||||||
MenuTabs: settingInfo.MenuTabs,
|
MenuTabs: settingInfo.MenuTabs,
|
||||||
PanelName: settingInfo.PanelName,
|
PanelName: settingInfo.PanelName,
|
||||||
Theme: settingInfo.Theme,
|
Theme: settingInfo.Theme,
|
||||||
|
NeedCaptcha: needCaptcha,
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, res)
|
helper.SuccessWithData(c, res)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ type MfaCredential struct {
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
IgnoreCaptcha bool `json:"ignoreCaptcha"`
|
|
||||||
Captcha string `json:"captcha"`
|
Captcha string `json:"captcha"`
|
||||||
CaptchaID string `json:"captchaID"`
|
CaptchaID string `json:"captchaID"`
|
||||||
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES'"`
|
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES'"`
|
||||||
|
|
|
||||||
|
|
@ -249,4 +249,5 @@ type LoginSetting struct {
|
||||||
MenuTabs string `json:"menuTabs"`
|
MenuTabs string `json:"menuTabs"`
|
||||||
PanelName string `json:"panelName"`
|
PanelName string `json:"panelName"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
|
NeedCaptcha bool `json:"needCaptcha"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package service
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||||
|
|
@ -13,6 +11,7 @@ import (
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthService struct{}
|
type AuthService struct{}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package global
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/core/init/auth"
|
||||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
|
@ -28,6 +29,8 @@ var (
|
||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
|
|
||||||
ScriptSyncJobID cron.EntryID
|
ScriptSyncJobID cron.EntryID
|
||||||
|
|
||||||
|
IPTracker *auth.IPTracker
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBOption func(*gorm.DB) *gorm.DB
|
type DBOption func(*gorm.DB) *gorm.DB
|
||||||
|
|
|
||||||
99
core/init/auth/ip_tracker.go
Normal file
99
core/init/auth/ip_tracker.go
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxIPCount = 100
|
||||||
|
ExpireDuration = 30 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPRecord struct {
|
||||||
|
NeedCaptcha bool
|
||||||
|
LastUpdate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPTracker struct {
|
||||||
|
records map[string]*IPRecord
|
||||||
|
ipOrder []string
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIPTracker() *IPTracker {
|
||||||
|
return &IPTracker{
|
||||||
|
records: make(map[string]*IPRecord),
|
||||||
|
ipOrder: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IPTracker) NeedCaptcha(ip string) bool {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
record, exists := t.records[ip]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(record.LastUpdate) > ExpireDuration {
|
||||||
|
t.removeIPUnsafe(ip)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return record.NeedCaptcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IPTracker) SetNeedCaptcha(ip string) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if record, exists := t.records[ip]; exists {
|
||||||
|
if time.Since(record.LastUpdate) > ExpireDuration {
|
||||||
|
t.removeIPUnsafe(ip)
|
||||||
|
} else {
|
||||||
|
record.NeedCaptcha = true
|
||||||
|
record.LastUpdate = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.records) >= MaxIPCount {
|
||||||
|
t.removeOldestUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.records[ip] = &IPRecord{
|
||||||
|
NeedCaptcha: true,
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
}
|
||||||
|
t.ipOrder = append(t.ipOrder, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IPTracker) Clear(ip string) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
t.removeIPUnsafe(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IPTracker) removeIPUnsafe(ip string) {
|
||||||
|
delete(t.records, ip)
|
||||||
|
|
||||||
|
for i, storedIP := range t.ipOrder {
|
||||||
|
if storedIP == ip {
|
||||||
|
t.ipOrder = append(t.ipOrder[:i], t.ipOrder[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IPTracker) removeOldestUnsafe() {
|
||||||
|
if len(t.ipOrder) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldestIP := t.ipOrder[0]
|
||||||
|
delete(t.records, oldestIP)
|
||||||
|
t.ipOrder = t.ipOrder[1:]
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/init/auth"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -54,6 +55,8 @@ func Start() {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global.IPTracker = auth.NewIPTracker()
|
||||||
|
|
||||||
tcpItem := "tcp4"
|
tcpItem := "tcp4"
|
||||||
if global.CONF.Conn.Ipv6 == constant.StatusEnable {
|
if global.CONF.Conn.Ipv6 == constant.StatusEnable {
|
||||||
tcpItem = "tcp"
|
tcpItem = "tcp"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ export namespace Login {
|
||||||
export interface ReqLoginForm {
|
export interface ReqLoginForm {
|
||||||
name: string;
|
name: string;
|
||||||
password: string;
|
password: string;
|
||||||
ignoreCaptcha: boolean;
|
|
||||||
captcha: string;
|
captcha: string;
|
||||||
captchaID: string;
|
captchaID: string;
|
||||||
authMethod: string;
|
authMethod: string;
|
||||||
|
|
@ -36,5 +35,6 @@ export namespace Login {
|
||||||
panelName: string;
|
panelName: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
isOffLine: boolean;
|
isOffLine: boolean;
|
||||||
|
needCaptcha: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,6 @@ const loginFormRef = ref<FormInstance>();
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
password: '',
|
password: '',
|
||||||
ignoreCaptcha: true,
|
|
||||||
captcha: '',
|
captcha: '',
|
||||||
captchaID: '',
|
captchaID: '',
|
||||||
authMethod: 'session',
|
authMethod: 'session',
|
||||||
|
|
@ -318,7 +317,6 @@ const login = (formEl: FormInstance | undefined) => {
|
||||||
let requestLoginForm = {
|
let requestLoginForm = {
|
||||||
name: loginForm.name,
|
name: loginForm.name,
|
||||||
password: encryptPassword(loginForm.password),
|
password: encryptPassword(loginForm.password),
|
||||||
ignoreCaptcha: globalStore.ignoreCaptcha,
|
|
||||||
captcha: loginForm.captcha,
|
captcha: loginForm.captcha,
|
||||||
captchaID: captcha.captchaID,
|
captchaID: captcha.captchaID,
|
||||||
authMethod: 'session',
|
authMethod: 'session',
|
||||||
|
|
@ -418,6 +416,7 @@ const getSetting = async () => {
|
||||||
isFxplay.value = res.data.isFxplay;
|
isFxplay.value = res.data.isFxplay;
|
||||||
globalStore.isFxplay = isFxplay.value;
|
globalStore.isFxplay = isFxplay.value;
|
||||||
globalStore.isOffLine = res.data.isOffLine;
|
globalStore.isOffLine = res.data.isOffLine;
|
||||||
|
globalStore.ignoreCaptcha = !res.data.needCaptcha;
|
||||||
|
|
||||||
document.title = res.data.panelName;
|
document.title = res.data.panelName;
|
||||||
i18n.warnHtmlMessage = false;
|
i18n.warnHtmlMessage = false;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue