feat(login): 增加登录 IP 地区闸门限制
基于 config.yaml 的 loginIpFilter 配置,仅允许中国大陆网段、内部保留地址及 allowIps 中的 IP 登录,拒绝境外 IP,并新增不可伪造的 GetRealClientIP 获取客户端真实 IP。
This commit is contained in:
@@ -9,6 +9,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/service"
|
||||
"github.com/tiger1103/gfast/v3/library/geoallow"
|
||||
"github.com/tiger1103/gfast/v3/library/libUtils"
|
||||
)
|
||||
|
||||
@@ -38,6 +40,21 @@ func (c *loginController) Login(ctx context.Context, req *system.UserLoginReq) (
|
||||
permissions []string
|
||||
menuList []*model.UserMenus
|
||||
)
|
||||
// 登录地区 IP 闸门: 非中国大陆(非内部)地址一律禁止登录。先于验证码/密码校验执行, 失败不泄露账号信息。
|
||||
ip := libUtils.GetRealClientIP(ctx)
|
||||
userAgent := libUtils.GetUserAgent(ctx)
|
||||
if err = loginCheckIPAllow(ctx, ip); err != nil {
|
||||
// 保存登录失败的日志信息
|
||||
service.SysLoginLog().Invoke(gctx.New(), &model.LoginLogParams{
|
||||
Status: 0,
|
||||
Username: req.Username,
|
||||
Ip: ip,
|
||||
UserAgent: userAgent,
|
||||
Msg: err.Error(),
|
||||
Module: "系统后台",
|
||||
})
|
||||
return
|
||||
}
|
||||
//判断验证码是否正确
|
||||
debug := gmode.IsDevelop()
|
||||
if !debug {
|
||||
@@ -46,8 +63,6 @@ func (c *loginController) Login(ctx context.Context, req *system.UserLoginReq) (
|
||||
return
|
||||
}
|
||||
}
|
||||
ip := libUtils.GetClientIp(ctx)
|
||||
userAgent := libUtils.GetUserAgent(ctx)
|
||||
user, err = service.SysUser().GetAdminUserByUsernamePassword(ctx, req)
|
||||
if err != nil {
|
||||
// 保存登录失败的日志信息
|
||||
@@ -117,3 +132,27 @@ func (c *loginController) LoginOut(ctx context.Context, req *system.UserLoginOut
|
||||
err = service.GfToken().RemoveToken(ctx, service.GfToken().GetRequestToken(g.RequestFromCtx(ctx)))
|
||||
return
|
||||
}
|
||||
|
||||
// loginCheckIPAllow 登录地区 IP 闸门判定。
|
||||
//
|
||||
// 配置位于 config.yaml 的 loginIpFilter 段:
|
||||
// - enabled: 总开关, 默认开启; 关闭则一律放行(紧急恢复手段)
|
||||
// - allowIps: 显式放行 IP 列表(不受地区限制), 如办公网/跳板出口公网 IP
|
||||
// - denyMsg: 拒绝时返回给前端的文案
|
||||
//
|
||||
// 放行条件: 开关关闭 / 命中 allowIps / 内部·保留地址(环回、内网、链路本地等) /
|
||||
// 中国大陆分配网段。境外及港澳台地址返回拒绝错误。
|
||||
func loginCheckIPAllow(ctx context.Context, ip string) error {
|
||||
if !g.Cfg().MustGet(ctx, "loginIpFilter.enabled", true).Bool() {
|
||||
return nil
|
||||
}
|
||||
for _, a := range g.Cfg().MustGet(ctx, "loginIpFilter.allowIps").Strings() {
|
||||
if strings.TrimSpace(a) == ip {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if geoallow.Allow(ip) {
|
||||
return nil
|
||||
}
|
||||
return gerror.New(g.Cfg().MustGet(ctx, "loginIpFilter.denyMsg", "当前网络环境不允许登录").String())
|
||||
}
|
||||
|
||||
@@ -56,6 +56,22 @@ func GetClientIp(ctx context.Context) string {
|
||||
return g.RequestFromCtx(ctx).GetClientIp()
|
||||
}
|
||||
|
||||
// GetRealClientIP 获取不可伪造的真实客户端IP。
|
||||
// 优先取 X-Real-IP(部署 nginx 以 $remote_addr 覆盖写入, 客户端无法伪造);
|
||||
// 否则回退 RemoteAddr(TCP 对端)。
|
||||
// 不采信 X-Forwarded-For 的首个值: 其采用追加语义, 首段可由客户端注入, 用作
|
||||
// 登录地区闸门判据时会被伪造绕过。多跳代理部署下须保证最终前置 nginx 不把
|
||||
// X-Real-IP 覆盖成代理自身地址(单跳时即真实客户端)。
|
||||
func GetRealClientIP(ctx context.Context) string {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
if ip := strings.TrimSpace(r.Header.Get("X-Real-IP")); ip != "" {
|
||||
if net.ParseIP(ip) != nil {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
return r.GetRemoteIp()
|
||||
}
|
||||
|
||||
// GetUserAgent 获取user-agent
|
||||
func GetUserAgent(ctx context.Context) string {
|
||||
return ghttp.RequestFromCtx(ctx).Header.Get("User-Agent")
|
||||
|
||||
@@ -64,6 +64,12 @@ gfToken:
|
||||
excludePaths:
|
||||
- "/api/v1/system/login"
|
||||
|
||||
# 登录 IP 地区闸门: 开启后仅允许「中国大陆网段 / 内部·保留地址 / allowIps」的 IP 登录,
|
||||
# 国外 IP 一律拒绝。enabled 置 false 可整体关闭(紧急恢复), 不影响已登录会话。
|
||||
loginIpFilter:
|
||||
enabled: true
|
||||
allowIps: [] # 显式放行 IP(不受地区限制), 如办公网/跳板出口公网 IP
|
||||
denyMsg: "当前网络环境不允许登录"
|
||||
|
||||
# Redis 配置示例
|
||||
redis:
|
||||
|
||||
Reference in New Issue
Block a user