mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +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 {
 | 
			
		||||
	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,14 +39,21 @@ Tea.context(function () {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this.submitSuccess = function (resp) {
 | 
			
		||||
		if (resp.data.requireOTP) {
 | 
			
		||||
			window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if (this.from.length == 0) {
 | 
			
		||||
			window.location = "/dashboard";
 | 
			
		||||
		} else {
 | 
			
		||||
			window.location = this.from;
 | 
			
		||||
		}
 | 
			
		||||
		// 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
 | 
			
		||||
			}
 | 
			
		||||
			if (this.from.length == 0) {
 | 
			
		||||
				window.location = "/dashboard";
 | 
			
		||||
			} else {
 | 
			
		||||
				window.location = this.from;
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	};
 | 
			
		||||
});
 | 
			
		||||
@@ -22,10 +22,17 @@ Tea.context(function () {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this.submitSuccess = function (resp) {
 | 
			
		||||
		if (this.from.length == 0) {
 | 
			
		||||
			window.location = "/dashboard";
 | 
			
		||||
		} else {
 | 
			
		||||
			window.location = this.from;
 | 
			
		||||
		}
 | 
			
		||||
		// 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