基于 config.yaml 的 loginIpFilter 配置,仅允许中国大陆网段、内部保留地址及 allowIps 中的 IP 登录,拒绝境外 IP,并新增不可伪造的 GetRealClientIP 获取客户端真实 IP。
159 lines
4.9 KiB
Go
159 lines
4.9 KiB
Go
/*
|
|
* @desc:登录
|
|
* @company:云南奇讯科技有限公司
|
|
* @Author: yixiaohu
|
|
* @Date: 2022/4/27 21:52
|
|
*/
|
|
|
|
package controller
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/crypto/gmd5"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gctx"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
"github.com/gogf/gf/v2/util/gmode"
|
|
"github.com/tiger1103/gfast/v3/api/v1/system"
|
|
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"
|
|
)
|
|
|
|
var (
|
|
Login = loginController{}
|
|
)
|
|
|
|
type loginController struct {
|
|
BaseController
|
|
}
|
|
|
|
func (c *loginController) Login(ctx context.Context, req *system.UserLoginReq) (res *system.UserLoginRes, err error) {
|
|
var (
|
|
user *model.LoginUserRes
|
|
token string
|
|
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 {
|
|
if !commonService.Captcha().VerifyString(req.VerifyKey, req.VerifyCode) {
|
|
err = gerror.New("验证码输入错误")
|
|
return
|
|
}
|
|
}
|
|
user, err = service.SysUser().GetAdminUserByUsernamePassword(ctx, req)
|
|
if err != nil {
|
|
// 保存登录失败的日志信息
|
|
service.SysLoginLog().Invoke(gctx.New(), &model.LoginLogParams{
|
|
Status: 0,
|
|
Username: req.Username,
|
|
Ip: ip,
|
|
UserAgent: userAgent,
|
|
Msg: err.Error(),
|
|
Module: "系统后台",
|
|
})
|
|
return
|
|
}
|
|
err = service.SysUser().UpdateLoginInfo(ctx, user.Id, ip)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// 报存登录成功的日志信息
|
|
service.SysLoginLog().Invoke(gctx.New(), &model.LoginLogParams{
|
|
Status: 1,
|
|
Username: req.Username,
|
|
Ip: ip,
|
|
UserAgent: userAgent,
|
|
Msg: "登录成功",
|
|
Module: "系统后台",
|
|
})
|
|
key := gconv.String(user.Id) + "-" + gmd5.MustEncryptString(user.UserName) + gmd5.MustEncryptString(user.UserPassword)
|
|
if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() {
|
|
key = gconv.String(user.Id) + "-" + gmd5.MustEncryptString(user.UserName) + gmd5.MustEncryptString(user.UserPassword+ip+userAgent)
|
|
}
|
|
user.UserPassword = ""
|
|
token, err = service.GfToken().GenerateToken(ctx, key, user)
|
|
g.Log().Debugf(ctx, "==========================key:%v;user:%v;token:%v==========================", key, user, token)
|
|
if err != nil {
|
|
g.Log().Error(ctx, err)
|
|
err = gerror.New("登录失败,后端服务出现错误")
|
|
return
|
|
}
|
|
_, err = service.ModuleTenant().AddRedisByTenantId(ctx, &system.AddRedisByTenantIdReq{TenantId: user.TenantId})
|
|
if err != nil {
|
|
return
|
|
}
|
|
//获取用户菜单数据
|
|
menuList, permissions, err = service.SysUser().GetAdminRules(ctx, user.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
res = &system.UserLoginRes{
|
|
UserInfo: user,
|
|
Token: token,
|
|
MenuList: menuList,
|
|
Permissions: permissions,
|
|
}
|
|
//用户在线状态保存
|
|
service.SysUserOnline().Invoke(gctx.New(), &model.SysUserOnlineParams{
|
|
UserAgent: userAgent,
|
|
Uuid: gmd5.MustEncrypt(token),
|
|
Token: token,
|
|
Username: user.UserName,
|
|
Ip: ip,
|
|
})
|
|
return
|
|
}
|
|
|
|
// LoginOut 退出登录
|
|
func (c *loginController) LoginOut(ctx context.Context, req *system.UserLoginOutReq) (res *system.UserLoginOutRes, err error) {
|
|
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())
|
|
}
|