diff --git a/internal/configloaders/security_config.go b/internal/configloaders/security_config.go index 8b33f55c..76d71672 100644 --- a/internal/configloaders/security_config.go +++ b/internal/configloaders/security_config.go @@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) { return nil, err } - v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig) + var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig) return &v, nil } @@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) { return sharedSecurityConfig, nil } - config := &systemconfigs.SecurityConfig{} + var config = &systemconfigs.SecurityConfig{ + Frame: FrameSameOrigin, + AllowLocal: true, + CheckClientFingerprint: false, + CheckClientRegion: true, + } err = json.Unmarshal(resp.ValueJSON, config) if err != nil { logs.Println("[SECURITY_MANAGER]" + err.Error()) @@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) { func defaultSecurityConfig() *systemconfigs.SecurityConfig { return &systemconfigs.SecurityConfig{ - Frame: FrameSameOrigin, - AllowLocal: true, + Frame: FrameSameOrigin, + AllowLocal: true, + CheckClientFingerprint: false, + CheckClientRegion: true, } } diff --git a/internal/nodes/admin_node.go b/internal/nodes/admin_node.go index 016525eb..281dc493 100644 --- a/internal/nodes/admin_node.go +++ b/internal/nodes/admin_node.go @@ -85,6 +85,9 @@ func (this *AdminNode) Run() { // 启动API节点 this.startAPINode() + // 启动IP库 + this.startIPLibrary() + // 启动Web服务 sessionManager, err := NewSessionManager() if err != nil { diff --git a/internal/nodes/admin_node_ext.go b/internal/nodes/admin_node_ext.go new file mode 100644 index 00000000..b4c99ed4 --- /dev/null +++ b/internal/nodes/admin_node_ext.go @@ -0,0 +1,18 @@ +// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . +//go:build !plus + +package nodes + +import ( + "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" + "github.com/iwind/TeaGo/logs" +) + +// 启动IP库 +func (this *AdminNode) startIPLibrary() { + logs.Println("NODE", "initializing ip library ...") + err := iplibrary.InitDefault() + if err != nil { + logs.Println("NODE", "initialize ip library failed: "+err.Error()) + } +} diff --git a/internal/rpc/rpc_utils.go b/internal/rpc/rpc_utils.go index 35ea970e..e916bb85 100644 --- a/internal/rpc/rpc_utils.go +++ b/internal/rpc/rpc_utils.go @@ -2,6 +2,9 @@ package rpc import ( "github.com/TeaOSLab/EdgeAdmin/internal/configs" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "strings" "sync" ) @@ -28,3 +31,23 @@ func SharedRPC() (*RPCClient, error) { sharedRPC = client return sharedRPC, nil } + +// IsConnError 是否为连接错误 +func IsConnError(err error) bool { + if err == nil { + return false + } + + // 检查是否为连接错误 + statusErr, ok := status.FromError(err) + if ok { + var errorCode = statusErr.Code() + return errorCode == codes.Unavailable || errorCode == codes.Canceled + } + + if strings.Contains(err.Error(), "code = Canceled") { + return true + } + + return false +} diff --git a/internal/web/actions/default/index/loginutils/utils.go b/internal/web/actions/default/index/loginutils/utils.go index 25308de7..8675f964 100644 --- a/internal/web/actions/default/index/loginutils/utils.go +++ b/internal/web/actions/default/index/loginutils/utils.go @@ -4,16 +4,41 @@ package loginutils import ( teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" "github.com/iwind/TeaGo/actions" stringutil "github.com/iwind/TeaGo/utils/string" + "net" "net/http" ) // CalculateClientFingerprint 计算客户端指纹 func CalculateClientFingerprint(action *actions.ActionObject) string { - return stringutil.Md5(action.RequestRemoteIP() + "@" + action.Request.UserAgent()) + return stringutil.Md5(RemoteIP(action) + "@" + action.Request.UserAgent()) } +// RemoteIP 获取客户端IP +// TODO 将来增加是否使用代理设置(即从X-Real-IP中获取IP) +func RemoteIP(action *actions.ActionObject) string { + ip, _, _ := net.SplitHostPort(action.Request.RemoteAddr) + return ip +} + +// LookupIPRegion 查找登录区域 +func LookupIPRegion(ip string) string { + if len(ip) == 0 { + return "" + } + + var result = iplibrary.LookupIP(ip) + if result != nil && result.IsOk() { + // 这里不需要网络运营商信息 + return result.CountryName() + "@" + result.ProvinceName() + "@" + result.CityName() + "@" + result.TownName() + } + + return "" +} + +// SetCookie 设置Cookie func SetCookie(action *actions.ActionObject, remember bool) { if remember { var cookie = &http.Cookie{ @@ -44,6 +69,7 @@ func SetCookie(action *actions.ActionObject, remember bool) { } } +// UnsetCookie 重置Cookie func UnsetCookie(action *actions.ActionObject) { cookie := &http.Cookie{ Name: teaconst.CookieSID, diff --git a/internal/web/actions/default/settings/security/index.go b/internal/web/actions/default/settings/security/index.go index 506eb753..0381a6f1 100644 --- a/internal/web/actions/default/settings/security/index.go +++ b/internal/web/actions/default/settings/security/index.go @@ -79,6 +79,9 @@ func (this *IndexAction) RunPost(params struct { DenySearchEngines bool DenySpiders bool + CheckClientFingerprint bool + CheckClientRegion bool + DomainsJSON []byte Must *actions.Must @@ -150,6 +153,10 @@ func (this *IndexAction) RunPost(params struct { // 允许记住登录 config.AllowRememberLogin = params.AllowRememberLogin + // Cookie检查 + config.CheckClientFingerprint = params.CheckClientFingerprint + config.CheckClientRegion = params.CheckClientRegion + err = configloaders.UpdateSecurityConfig(config) if err != nil { this.ErrorPage(err) diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index 7281e436..39d787e3 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -175,13 +175,32 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam } // 检查指纹 - var clientFingerprint = session.GetString("@fingerprint") - if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) { - loginutils.UnsetCookie(action) - session.Delete() + if securityConfig != nil && securityConfig.CheckClientFingerprint { + var clientFingerprint = session.GetString("@fingerprint") + if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) { + loginutils.UnsetCookie(action) + session.Delete() - this.login(action) - return false + this.login(action) + return false + } + } + + // 检查区域 + 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 { + loginutils.UnsetCookie(action) + session.Delete() + + this.login(action) + return false + } + } } // 检查用户是否存在 diff --git a/internal/web/helpers/user_should_auth.go b/internal/web/helpers/user_should_auth.go index dcd2cc61..e9cfd4c9 100644 --- a/internal/web/helpers/user_should_auth.go +++ b/internal/web/helpers/user_should_auth.go @@ -58,6 +58,7 @@ func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) { 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)) } func (this *UserShouldAuth) IsUser() bool { diff --git a/web/views/@default/settings/security/index.html b/web/views/@default/settings/security/index.html index 1ab25300..ef3feff3 100644 --- a/web/views/@default/settings/security/index.html +++ b/web/views/@default/settings/security/index.html @@ -77,6 +77,20 @@
只允许通过这些域名(或者IP作为主机地址)访问当前管理系统,不填表示没有限制。
+选中后,表示每次访问时都检查客户端相关信息是否跟登录时一致。
+选中后,表示每次访问时都检查客户端所在地理区域是否和登录时一致。
+