mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-03 12:20:28 +08:00
使用本地SID二次校验增强管理系统安全性
This commit is contained in:
@@ -72,6 +72,9 @@ func (this *SessionManager) Read(sid string) map[string]string {
|
||||
}
|
||||
|
||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
@@ -95,6 +98,9 @@ func (this *SessionManager) WriteItem(sid string, key string, value string) bool
|
||||
}
|
||||
|
||||
func (this *SessionManager) Delete(sid string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net"
|
||||
@@ -236,7 +237,10 @@ func (this *IndexAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 写入SESSION
|
||||
params.Auth.StoreAdmin(adminId, params.Remember)
|
||||
var localSid = rands.HexString(32)
|
||||
this.Data["localSid"] = localSid
|
||||
this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject)
|
||||
params.Auth.StoreAdmin(adminId, params.Remember, localSid)
|
||||
|
||||
// 记录日志
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSuccess, []any{params.Username})
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"github.com/xlzd/gotp"
|
||||
"time"
|
||||
@@ -132,7 +133,10 @@ func (this *OtpAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 写入SESSION
|
||||
params.Auth.StoreAdmin(adminId, params.Remember)
|
||||
var localSid = rands.HexString(32)
|
||||
this.Data["localSid"] = localSid
|
||||
this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject)
|
||||
params.Auth.StoreAdmin(adminId, params.Remember, localSid)
|
||||
|
||||
// 删除OTP SESSION
|
||||
_, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid})
|
||||
|
||||
14
internal/web/actions/default/login/init.go
Normal file
14
internal/web/actions/default/login/init.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/login").
|
||||
GetPost("/validate", new(ValidateAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
74
internal/web/actions/default/login/validate.go
Normal file
74
internal/web/actions/default/login/validate.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ValidateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ValidateAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ValidateAction) RunGet(params struct {
|
||||
From string
|
||||
}) {
|
||||
this.Data["from"] = params.From
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *ValidateAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
|
||||
LocalSid string
|
||||
Ip string
|
||||
}) {
|
||||
var isOk bool
|
||||
|
||||
defer func() {
|
||||
this.Data["isOk"] = isOk
|
||||
|
||||
if !isOk {
|
||||
loginutils.UnsetCookie(&this.ActionObject)
|
||||
this.Session().Delete()
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}()
|
||||
|
||||
if len(params.LocalSid) == 0 || len(params.LocalSid) != 32 {
|
||||
return
|
||||
}
|
||||
if len(params.Ip) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if net.ParseIP(params.Ip) == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if params.LocalSid == this.Session().GetString("@localSid") {
|
||||
isOk = true
|
||||
|
||||
// renew ip and local sid
|
||||
var newIP = loginutils.RemoteIP(&this.ActionObject)
|
||||
var newLocalSid = rands.HexString(32)
|
||||
|
||||
this.Session().Write("@ip", newIP)
|
||||
this.Session().Write("@localSid", newLocalSid)
|
||||
|
||||
this.Data["ip"] = newIP
|
||||
this.Data["localSid"] = newLocalSid
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -200,18 +200,22 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
}
|
||||
|
||||
// 检查区域
|
||||
if securityConfig != nil && securityConfig.CheckClientRegion {
|
||||
var oldClientIP = session.GetString("@ip")
|
||||
var currentClientIP = loginutils.RemoteIP(action)
|
||||
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
|
||||
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
|
||||
var newRegion = loginutils.LookupIPRegion(currentClientIP)
|
||||
if newRegion != oldRegion {
|
||||
if securityConfig != nil && securityConfig.CheckClientRegion {
|
||||
loginutils.UnsetCookie(action)
|
||||
session.Delete()
|
||||
|
||||
this.login(action)
|
||||
return false
|
||||
} else {
|
||||
// TODO 考虑IP变化时也需要验证,主要是考虑被反向代理的情形
|
||||
action.RedirectURL("/login/validate?from=" + url.QueryEscape(action.Request.URL.String()))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,13 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
||||
}
|
||||
|
||||
// StoreAdmin 存储用户名到SESSION
|
||||
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
|
||||
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool, localSid string) {
|
||||
loginutils.SetCookie(this.action, remember)
|
||||
var session = this.action.Session()
|
||||
session.Write("adminId", numberutils.FormatInt64(adminId))
|
||||
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
|
||||
session.Write("@ip", loginutils.RemoteIP(this.action))
|
||||
session.Write("@localSid", localSid)
|
||||
}
|
||||
|
||||
func (this *UserShouldAuth) IsUser() bool {
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/tasks"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/log"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/login"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/logout"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/messages"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes"
|
||||
|
||||
@@ -39,6 +39,12 @@ Tea.context(function () {
|
||||
};
|
||||
|
||||
this.submitSuccess = function (resp) {
|
||||
// store information to local
|
||||
localStorage.setItem("sid", resp.data.localSid)
|
||||
localStorage.setItem("ip", resp.data.ip)
|
||||
|
||||
// redirect back
|
||||
this.$delay(function () {
|
||||
if (resp.data.requireOTP) {
|
||||
window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from)
|
||||
return
|
||||
@@ -48,5 +54,6 @@ Tea.context(function () {
|
||||
} else {
|
||||
window.location = this.from;
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
@@ -22,10 +22,17 @@ Tea.context(function () {
|
||||
};
|
||||
|
||||
this.submitSuccess = function (resp) {
|
||||
// store information to local
|
||||
localStorage.setItem("sid", resp.data.localSid)
|
||||
localStorage.setItem("ip", resp.data.ip)
|
||||
|
||||
// redirect back
|
||||
this.$delay(function () {
|
||||
if (this.from.length == 0) {
|
||||
window.location = "/dashboard";
|
||||
} else {
|
||||
window.location = this.from;
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
15
web/views/@default/login/validate.html
Normal file
15
web/views/@default/login/validate.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
{$TEA.VUE}
|
||||
{$TEA.SEMANTIC}
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
34
web/views/@default/login/validate.js
Normal file
34
web/views/@default/login/validate.js
Normal file
@@ -0,0 +1,34 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
let sid = localStorage.getItem("sid")
|
||||
let ip = localStorage.getItem("ip")
|
||||
|
||||
if (sid == null || sid.length == 0 || ip == null || ip.length == 0) {
|
||||
window.location = "/logout"
|
||||
return
|
||||
}
|
||||
|
||||
this.$post("$")
|
||||
.params({localSid: sid, "ip": ip})
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
if (!resp.data.isOk) {
|
||||
window.location = "/logout"
|
||||
return
|
||||
}
|
||||
|
||||
// renew local data
|
||||
localStorage.setItem("sid", resp.data.localSid)
|
||||
localStorage.setItem("ip", resp.data.ip)
|
||||
|
||||
// redirect back (MUST delay)
|
||||
this.$delay(function () {
|
||||
if (this.from.length > 0) {
|
||||
window.location = this.from
|
||||
} else {
|
||||
window.location = "/dashboard"
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user