From 741a5d4bc62b325484b25a829ad5bb5f7a461024 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Wed, 6 Sep 2023 15:12:51 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=99=BB=E5=BD=95=E5=92=8C?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/security/account.go | 173 +++++++++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 5 + lib/routes/routes.go | 8 ++ 4 files changed, 190 insertions(+), 1 deletion(-) diff --git a/features/security/account.go b/features/security/account.go index cc2c9e13..8870b935 100644 --- a/features/security/account.go +++ b/features/security/account.go @@ -3,12 +3,18 @@ package security import ( "encoding/json" "fmt" + "image/color" "io" "net/http" + "strconv" + "time" "ems.agt/features/security/service" + sysConfigService "ems.agt/features/sys_config/service" "ems.agt/lib/core/account" + "ems.agt/lib/core/cache" "ems.agt/lib/core/conf" + "ems.agt/lib/core/constants/cachekey" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/vo/result" "ems.agt/lib/dborm" @@ -17,6 +23,8 @@ import ( "ems.agt/lib/oauth" "ems.agt/lib/services" "ems.agt/restagent/config" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/mojocn/base64Captcha" ) var ( @@ -26,6 +34,14 @@ var ( CustomUriOauthToken = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token" CustomUriOauthHandshake = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake" + // 系统登录 + UriLogin = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/login" + CustomUriLogin = config.UriPrefix + "/securityManagement/{apiVersion}/login" + + // 获取验证码 + UriCaptchaImage = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/captchaImage" + CustomUriCaptchaImage = config.UriPrefix + "/securityManagement/{apiVersion}/captchaImage" + // 登录用户信息 UriUserInfo = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getUserInfo" CustomUriUserInfo = config.UriPrefix + "/securityManagement/{apiVersion}/getUserInfo" @@ -208,6 +224,163 @@ func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) { return } +// 系统登录 +// +// POST /login +func LoginOMC(w http.ResponseWriter, r *http.Request) { + log.Info("LoginFromOMC processing... ") + var body struct { + Username string `json:"username" binding:"required"` // Username 用户名 + Password string `json:"password" binding:"required"` // Password 用户密码 + Code string `json:"code"` // Code 验证码 + UUID string `json:"uuid"` // UUID 验证码唯一标识 + } + err := ctx.ShouldBindJSON(r, &body) + if err != nil { + log.Error("Invalid Json Format") + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + // response 400-5 + if body.Username == "" || body.Password == "" { + log.Error("Wrong parameter value") + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + + // 校验验证码 + // 从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := sysConfigService.NewServiceSysConfig.SelectConfigValueByKey("sys.account.captchaEnabled") + captchaEnabled, err := strconv.ParseBool(captchaEnabledStr) + if err != nil { + captchaEnabled = false + } + if captchaEnabled { + if body.Code == "" || body.UUID == "" { + log.Error("Authentication failed, mismatch captcha") + ctx.JSON(w, 400, result.CodeMsg(400, "验证码信息错误")) + return + } + verifyKey := cachekey.CAPTCHA_CODE_KEY + body.UUID + captcha, ok := cache.GetLocalTTL(verifyKey) + if captcha == nil || !ok { + log.Error("Authentication failed, captcha emtry") + ctx.JSON(w, 400, result.CodeMsg(400, "验证码已失效")) + return + } + cache.DeleteLocalTTL(verifyKey) + if captcha.(string) != body.Code { + log.Error("Authentication failed, not match captcha") + ctx.JSON(w, 400, result.CodeMsg(400, "验证码错误")) + return + } + } + + validUser, user, err := dborm.XormCheckLoginUser(body.Username, body.Password, config.GetYamlConfig().Auth.Crypt) + if !validUser || err != nil { + // response 400-4 + log.Error("Authentication failed, mismatch user or password") + ctx.JSON(w, 400, result.CodeMsg(400, err.Error())) + return + } + + token := oauth.GenRandToken("omc") // Generate new token to session ID + sourceAddr := r.RemoteAddr + affected, err := dborm.XormInsertSession(body.Username, sourceAddr, token, + config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session) + if err != nil { + log.Error("Failed to XormInsertSession:", err) + if affected == -1 { + services.ResponseForbidden403MultiLoginNotAllowed(w) + } else { + services.ResponseBadRequest400IncorrectLogin(w) + } + + return + } + + if user != nil { + // 缓存用户信息 + account.CacheLoginUser(user) + ctx.JSON(w, 200, result.OkData(map[string]any{ + "accessToken": token, + })) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 获取验证码 +// +// GET /captchaImage +func CaptchaImage(w http.ResponseWriter, r *http.Request) { + configService := sysConfigService.NewServiceSysConfig + + // 从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := configService.SelectConfigValueByKey("sys.account.captchaEnabled") + captchaEnabled, err := strconv.ParseBool(captchaEnabledStr) + if err != nil { + captchaEnabled = false + } + if !captchaEnabled { + ctx.JSON(w, 200, result.Ok(map[string]any{ + "captchaEnabled": captchaEnabled, + })) + return + } + + // 生成唯一标识 + verifyKey := "" + data := map[string]any{ + "captchaEnabled": captchaEnabled, + "uuid": "", + "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + } + + // char 字符验证 + driverCaptcha := &base64Captcha.DriverString{ + //Height png height in pixel. + Height: 40, + // Width Captcha png width in pixel. + Width: 120, + //NoiseCount text noise count. + NoiseCount: 4, + //Length random string length. + Length: 4, + //Source is a unicode which is the rand string from. + Source: "023456789abcdefghjkmnprstuvwxyz", + //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . + ShowLineOptions: base64Captcha.OptionShowHollowLine, + //BgColor captcha image background color (optional) + BgColor: &color.RGBA{ + R: 250, + G: 250, + B: 250, + A: 255, // 不透明 + }, + } + // 验证码生成 + id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() + // 验证码表达式解析输出 + item, err := driverCaptcha.DrawCaptcha(question) + if err != nil { + logger.Infof("Generate Id Question Answer %s : %v", question, err) + } else { + data["uuid"] = id + data["img"] = item.EncodeB64string() + verifyKey = cachekey.CAPTCHA_CODE_KEY + id + cache.SetLocalTTL(verifyKey, answer, 120*time.Second) + } + + // 本地开发下返回验证码结果,方便接口调试 + // text, ok := cache.GetLocalTTL(verifyKey) + // if ok { + // data["text"] = text.(string) + // } + + ctx.JSON(w, 200, result.Ok(data)) +} + // 登录用户信息 func UserInfo(w http.ResponseWriter, r *http.Request) { loginUser, err := ctx.LoginUser(r) diff --git a/go.mod b/go.mod index 8bb90b9b..4cdb3132 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v9 v9.0.0-rc.1 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 // indirect @@ -86,6 +87,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mojocn/base64Captcha v1.3.5 github.com/nsqio/go-nsq v1.0.8 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect @@ -113,7 +115,8 @@ require ( github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/image v0.5.0 // indirect + golang.org/x/net v0.10.0 golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.6.0 // indirect diff --git a/go.sum b/go.sum index bb23c191..28897b8c 100644 --- a/go.sum +++ b/go.sum @@ -203,6 +203,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -486,6 +488,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= +github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -748,6 +752,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= diff --git a/lib/routes/routes.go b/lib/routes/routes.go index 2bcdb3e1..d6212c98 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -282,6 +282,14 @@ func init() { Register("POST", lm.ExtBackupDataUri, lm.ExtDatabaseBackupData, nil) Register("POST", lm.CustomExtBackupDataUri, lm.ExtDatabaseBackupData, nil) + // 系统登录 + Register("POST", security.UriLogin, security.LoginOMC, nil) + Register("POST", security.CustomUriLogin, security.LoginOMC, nil) + + // 获取验证码 + Register("GET", security.UriCaptchaImage, security.CaptchaImage, nil) + Register("GET", security.CustomUriCaptchaImage, security.CaptchaImage, nil) + // 登录用户信息 Register("GET", security.UriUserInfo, security.UserInfo, midware.Authorize(nil)) Register("GET", security.CustomUriUserInfo, security.UserInfo, midware.Authorize(nil))