feat: 新增第三方登录认证和管理

This commit is contained in:
TsMask
2025-08-12 10:12:46 +08:00
parent b7e4948fbb
commit 9e57c145d1
62 changed files with 2086 additions and 721 deletions

View File

@@ -23,38 +23,84 @@ func Setup(router *gin.Engine) {
)
// 账号身份操作
account := controller.NewAccount
accountGroup := router.Group("/auth")
{
router.POST("/auth/login",
accountGroup.POST("/login",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Login,
account.Login,
)
router.POST("/auth/logout",
accountGroup.POST("/logout",
middleware.RateLimit(middleware.LimitOption{
Time: 120,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Logout,
account.Logout,
)
router.POST("/auth/refresh-token",
accountGroup.POST("/refresh-token",
middleware.RateLimit(middleware.LimitOption{
Time: 60,
Count: 5,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.RefreshToken,
account.RefreshToken,
)
router.GET("/me",
middleware.AuthorizeUser(nil),
controller.NewAccount.Me,
account.Me,
)
router.GET("/router",
middleware.AuthorizeUser(nil),
controller.NewAccount.Router,
account.Router,
)
}
// 登录认证源
{
accountGroup.GET("/login/source",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 60,
Type: middleware.LIMIT_IP,
}),
account.LoginSource,
)
accountGroup.POST("/login/ldap",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.LDAP,
)
accountGroup.POST("/login/smtp",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.SMTP,
)
accountGroup.GET("/login/oauth2/authorize",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.OAuth2CodeURL,
)
accountGroup.POST("/login/oauth2/token",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.OAuth2Token,
)
}

View File

@@ -4,16 +4,13 @@ import (
"fmt"
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/service"
systemModelVO "be.ems/src/modules/system/model/vo"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
@@ -33,95 +30,6 @@ type AccountController struct {
sysLogLoginService *systemService.SysLogLogin // 系统登录访问
}
// Login 系统登录
//
// POST /auth/login
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login [post]
func (s AccountController) Login(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码 根据错误信息,创建系统访问记录
if err := s.accountService.ValidateCaptcha(body.Code, body.UUID); err != nil {
msg := fmt.Sprintf("%s code %s", err.Error(), body.Code)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 登录用户信息
info, err := s.accountService.ByUsername(body.Username, body.Password)
if err != nil {
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
if !config.IsSystemUser(info.UserId) {
// 强制改密码
forcePasswdChange, err := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if err != nil {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}
// Logout 系统登出
//
// POST /auth/logout
@@ -222,82 +130,10 @@ func (s AccountController) RefreshToken(c *gin.Context) {
}))
}
// Me 登录用户信息
// LoginSource 登录认证源
//
// GET /me
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Information
// @Description Login User Information
// @Router /me [get]
func (s AccountController) Me(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
info, err := reqctx.LoginUser(c)
if err != nil {
c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_INVALID, err.Error()))
return
}
// 角色权限集合,系统管理员拥有所有权限
isSystemUser := config.IsSystemUser(info.UserId)
roles, perms := s.accountService.RoleAndMenuPerms(info.UserId, isSystemUser)
info.User.NickName = i18n.TKey(language, info.User.NickName)
info.User.Remark = i18n.TKey(language, info.User.Remark)
info.User.Dept.DeptName = i18n.TKey(language, info.User.Dept.DeptName)
for ri := range info.User.Roles {
info.User.Roles[ri].RoleName = i18n.TKey(language, info.User.Roles[ri].RoleName)
}
data := map[string]any{
"user": info.User,
"roles": roles,
"permissions": perms,
}
if !isSystemUser {
// 强制改密码
forcePasswdChange, _ := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
// GET /auth/login/source
func (s AccountController) LoginSource(c *gin.Context) {
data := s.accountService.LoginSource()
c.JSON(200, resp.OkData(data))
}
// Router 登录用户路由信息
//
// GET /router
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Routing Information
// @Description Login User Routing Information
// @Router /router [get]
func (s AccountController) Router(c *gin.Context) {
loginUserId := reqctx.LoginUserToUserID(c)
// 前端路由,系统管理员拥有所有
isSystemUser := config.IsSystemUser(loginUserId)
buildMenus := s.accountService.RouteMenus(loginUserId, isSystemUser)
// 闭包函数处理多语言
language := reqctx.AcceptLanguage(c)
var converI18n func(language string, arr *[]systemModelVO.Router)
converI18n = func(language string, arr *[]systemModelVO.Router) {
for i := range *arr {
(*arr)[i].Meta.Title = i18n.TKey(language, (*arr)[i].Meta.Title)
if len((*arr)[i].Children) > 0 {
converI18n(language, &(*arr)[i].Children)
}
}
}
converI18n(language, &buildMenus)
c.JSON(200, resp.OkData(buildMenus))
}

View File

@@ -0,0 +1,93 @@
package controller
import (
"be.ems/src/framework/config"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
systemModelVO "be.ems/src/modules/system/model/vo"
"github.com/gin-gonic/gin"
)
// Me 登录用户信息
//
// GET /me
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Information
// @Description Login User Information
// @Router /me [get]
func (s AccountController) Me(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
info, err := reqctx.LoginUser(c)
if err != nil {
c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_INVALID, err.Error()))
return
}
// 角色权限集合,系统管理员拥有所有权限
isSystemUser := config.IsSystemUser(info.UserId)
roles, perms := s.accountService.RoleAndMenuPerms(info.UserId, isSystemUser)
info.User.NickName = i18n.TKey(language, info.User.NickName)
info.User.Remark = i18n.TKey(language, info.User.Remark)
if info.User.Dept != nil {
info.User.Dept.DeptName = i18n.TKey(language, info.User.Dept.DeptName)
}
for ri := range info.User.Roles {
info.User.Roles[ri].RoleName = i18n.TKey(language, info.User.Roles[ri].RoleName)
}
data := map[string]any{
"user": info.User,
"roles": roles,
"permissions": perms,
}
if !isSystemUser {
// 强制改密码
forcePasswdChange, _ := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
c.JSON(200, resp.OkData(data))
}
// Router 登录用户路由信息
//
// GET /router
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Routing Information
// @Description Login User Routing Information
// @Router /router [get]
func (s AccountController) Router(c *gin.Context) {
loginUserId := reqctx.LoginUserToUserID(c)
// 前端路由,系统管理员拥有所有
isSystemUser := config.IsSystemUser(loginUserId)
buildMenus := s.accountService.RouteMenus(loginUserId, isSystemUser)
// 闭包函数处理多语言
language := reqctx.AcceptLanguage(c)
var converI18n func(language string, arr *[]systemModelVO.Router)
converI18n = func(language string, arr *[]systemModelVO.Router) {
for i := range *arr {
(*arr)[i].Meta.Title = i18n.TKey(language, (*arr)[i].Meta.Title)
if len((*arr)[i].Children) > 0 {
converI18n(language, &(*arr)[i].Children)
}
}
}
converI18n(language, &buildMenus)
c.JSON(200, resp.OkData(buildMenus))
}

View File

@@ -0,0 +1,80 @@
package controller
import (
"fmt"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/modules/auth/model"
"github.com/gin-gonic/gin"
)
// LDAP LDAP认证登录
//
// POST /auth/ldap
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/ldap [post]
func (s AccountController) LDAP(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
info, err := s.accountService.ByLDAP(body)
if err != nil {
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}

View File

@@ -0,0 +1,107 @@
package controller
import (
"fmt"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/modules/auth/model"
"github.com/gin-gonic/gin"
)
// OAuth2CodeURL OAuth2认证跳转登录URL
//
// GET /auth/login/oauth2/authorize
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login/oauth2/authorize [get]
func (s AccountController) OAuth2CodeURL(c *gin.Context) {
state := c.Query("state")
if state == "" {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: state is empty"))
return
}
redirectURL, err := s.accountService.ByOAuth2CodeURL(state)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
c.Redirect(302, redirectURL)
}
// OAuth2 OAuth2认证登录
//
// POST /auth/login/oauth2/token
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login/oauth2/token [post]
func (s AccountController) OAuth2Token(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceOauth2Body
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
info, err := s.accountService.ByOAuth2(body)
if err != nil {
s.sysLogLoginService.Insert(
body.State, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.State, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}

View File

@@ -0,0 +1,80 @@
package controller
import (
"fmt"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/modules/auth/model"
"github.com/gin-gonic/gin"
)
// SMTP SMTP认证登录
//
// POST /auth/smtp
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/smtp [post]
func (s AccountController) SMTP(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
info, err := s.accountService.BySMTP(body)
if err != nil {
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}

View File

@@ -0,0 +1,104 @@
package controller
import (
"fmt"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/modules/auth/model"
"github.com/gin-gonic/gin"
)
// Login 系统登录
//
// POST /auth/login
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login [post]
func (s AccountController) Login(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码 根据错误信息,创建系统访问记录
if err := s.accountService.ValidateCaptcha(body.Code, body.UUID); err != nil {
msg := fmt.Sprintf("%s code %s", err.Error(), body.Code)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 登录用户信息
info, err := s.accountService.ByUsername(body.Username, body.Password)
if err != nil {
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
if !config.IsSystemUser(info.UserId) {
// 强制改密码
forcePasswdChange, err := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if err != nil {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}

View File

@@ -2,15 +2,8 @@ package model
// LoginBody 用户登录对象
type LoginBody struct {
// Username 用户名
Username string `json:"username" binding:"required"`
// Password 用户密码
Password string `json:"password" binding:"required"`
// Code 验证码
Code string `json:"code"`
// UUID 验证码唯一标识
UUID string `json:"uuid"`
Username string `json:"username" binding:"required"` // Username 用户名
Password string `json:"password" binding:"required"` // Password 用户密码
Code string `json:"code"` // Code 验证码
UUID string `json:"uuid"` // UUID 验证码唯一标识
}

View File

@@ -0,0 +1,22 @@
package model
// LoginSourceVo 认证源登录对象
type LoginSourceVo struct {
UID string `json:"uid"` // UID 认证源UID
Name string `json:"name"` // Name 认证源名称
Type string `json:"type"` // Type 认证源类型
Icon string `json:"icon"` // Icon 认证源图标
}
// LoginSourceBody 认证源用户登录对象
type LoginSourceBody struct {
Username string `json:"username" binding:"required"` // Username 用户名
Password string `json:"password" binding:"required"` // Password 用户密码
UID string `json:"uid" binding:"required"` // UID 认证源唯一标识
}
// LoginSourceOauth2Body 认证源OAuth2用户登录对象
type LoginSourceOauth2Body struct {
Code string `json:"code" binding:"required"` // Code 授权码
State string `json:"state" binding:"required"` // State 状态-认证源唯一标识
}

View File

@@ -2,21 +2,10 @@ package model
// RegisterBody 用户注册对象
type RegisterBody struct {
// Username 用户名
Username string `json:"username" binding:"required"`
// Password 用户密
Password string `json:"password" binding:"required"`
// ConfirmPassword 用户确认密码
ConfirmPassword string `json:"confirmPassword" binding:"required"`
// Code 验证码
Code string `json:"code"`
// UUID 验证码唯一标识
UUID string `json:"uuid"`
// UserType 标记用户类型
UserType string `json:"userType"`
Username string `json:"username" binding:"required"` // Username 用户名
Password string `json:"password" binding:"required"` // Password 用户密码
ConfirmPassword string `json:"confirmPassword" binding:"required"` // ConfirmPassword 用户确认密码
Code string `json:"code"` // Code 验证
UUID string `json:"uuid"` // UUID 验证码唯一标识
UserType string `json:"userType"` // UserType 标记用户类型
}

View File

@@ -2,102 +2,32 @@ package service
import (
"fmt"
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/database/redis"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/crypto"
"be.ems/src/framework/utils/parse"
systemModelVO "be.ems/src/modules/system/model/vo"
"be.ems/src/modules/auth/model"
systemModel "be.ems/src/modules/system/model"
systemService "be.ems/src/modules/system/service"
)
// 实例化服务层 Account 结构体
var NewAccount = &Account{
sysUserService: systemService.NewSysUser,
sysConfigService: systemService.NewSysConfig,
sysRoleService: systemService.NewSysRole,
sysMenuService: systemService.NewSysMenu,
sysUserService: systemService.NewSysUser,
sysConfigService: systemService.NewSysConfig,
sysRoleService: systemService.NewSysRole,
sysMenuService: systemService.NewSysMenu,
sysLogSourceService: systemService.NewSysLoginSource,
}
// 账号身份操作服务 服务层处理
type Account struct {
sysUserService *systemService.SysUser // 用户信息服务
sysConfigService *systemService.SysConfig // 参数配置服务
sysRoleService *systemService.SysRole // 角色服务
sysMenuService *systemService.SysMenu // 菜单服务
}
// ValidateCaptcha 校验验证码
func (s *Account) ValidateCaptcha(code, uuid string) error {
// 验证码检查,从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.FindValueByKey("sys.account.captchaEnabled")
if !parse.Boolean(captchaEnabledStr) {
return nil
}
if code == "" || uuid == "" {
// 验证码信息错误
return fmt.Errorf("captcha.err")
}
verifyKey := constants.CACHE_CAPTCHA_CODE + ":" + uuid
captcha, _ := redis.Get("", verifyKey)
if captcha == "" {
// 验证码已失效
return fmt.Errorf("captcha.errValid")
}
_ = redis.Del("", verifyKey)
if captcha != code {
// 验证码错误
return fmt.Errorf("captcha.err")
}
return nil
}
// ByUsername 登录创建用户信息
func (s Account) ByUsername(username, password string) (token.UserInfo, error) {
info := token.UserInfo{}
// 检查密码重试次数
retryKey, retryCount, lockTime, err := s.passwordRetryCount(username)
if err != nil {
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(username)
if sysUser.UserName != username {
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("login.errStatus")
}
// 检验用户密码
compareBool := crypto.BcryptCompare(password, sysUser.Password)
if compareBool {
s.CleanLoginRecordCache(sysUser.UserName) // 清除错误记录次数
} else {
_ = redis.Set("", retryKey, retryCount+1, lockTime)
return info, fmt.Errorf("login.errNameOrPasswd")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
sysUserService *systemService.SysUser // 用户信息服务
sysConfigService *systemService.SysConfig // 参数配置服务
sysRoleService *systemService.SysRole // 角色服务
sysMenuService *systemService.SysMenu // 菜单服务
sysLogSourceService *systemService.SysLoginSource // 认证源
}
// ByUserId 用户ID刷新令牌创建用户信息
@@ -140,86 +70,43 @@ func (s Account) UpdateLoginDateAndIP(info token.UserInfo) bool {
return s.sysUserService.Update(user) > 0
}
// CleanLoginRecordCache 清除错误记录次数
func (s Account) CleanLoginRecordCache(userName string) bool {
cacheKey := fmt.Sprintf("%s:%s", constants.CACHE_PWD_ERR_COUNT, userName)
hasKey, err := redis.Has("", cacheKey)
if hasKey > 0 && err == nil {
return redis.Del("", cacheKey) == nil
// LoginSource 登录认证源
func (s Account) LoginSource() []model.LoginSourceVo {
rows := s.sysLogSourceService.FindByActive("")
data := make([]model.LoginSourceVo, 0)
for _, v := range rows {
data = append(data, model.LoginSourceVo{
UID: v.UID,
Name: v.Name,
Type: v.Type,
Icon: v.Icon,
})
}
return false
return data
}
// passwordRetryCount 密码重试次数
func (s Account) passwordRetryCount(userName string) (string, int64, time.Duration, error) {
// 从数据库配置获取登录次数和错误锁定时间
maxRetryCountStr := s.sysConfigService.FindValueByKey("sys.user.maxRetryCount")
lockTimeStr := s.sysConfigService.FindValueByKey("sys.user.lockTime")
// 验证登录次数和错误锁定时间
maxRetryCount := parse.Number(maxRetryCountStr)
lockTime := parse.Number(lockTimeStr)
// initLoginSourceUser 初始化登录源用户
func (s *Account) initLoginSourceUser(uid, sType, username, password string) systemModel.SysUser {
sysUser := systemModel.SysUser{
UserName: username,
NickName: username, // 昵称使用名称账号
Password: password, // 原始密码
UserType: sType,
UserSource: uid,
Sex: "0", // 性别未选择
StatusFlag: constants.STATUS_YES, // 账号状态激活
DeptId: 101, // 归属部门为根节点
CreateBy: sType, // 创建来源
}
// 验证缓存记录次数
retryKey := fmt.Sprintf("%s:%s", constants.CACHE_PWD_ERR_COUNT, userName)
retryCount, err := redis.Get("", retryKey)
if retryCount == "" || err != nil {
retryCount = "0"
// 新增用户的角色管理
sysUser.RoleIds = []int64{5}
// 新增用户的岗位管理
sysUser.PostIds = []int64{}
insertId := s.sysUserService.Insert(sysUser)
if insertId > 0 {
sysUser.UserId = insertId
}
// 是否超过错误值
retryCountInt64 := parse.Number(retryCount)
if retryCountInt64 >= int64(maxRetryCount) {
// msg := fmt.Sprintf("密码输入错误 %d 次,帐户锁定 %d 分钟", maxRetryCount, lockTime)
msg := fmt.Errorf("login.errRetryPasswd") // 密码输入错误多次,帐户已被锁定
return retryKey, retryCountInt64, time.Duration(lockTime) * time.Minute, fmt.Errorf("%s", msg)
}
return retryKey, retryCountInt64, time.Duration(lockTime) * time.Minute, nil
}
// PasswordCountOrExpireTime 首次登录或密码过期时间
func (s Account) PasswordCountOrExpireTime(loginCount, passwordUpdateTime int64) (bool, error) {
forcePasswdChange := false
// 从数据库配置获取-首次登录密码修改
fristPasswdChangeStr := s.sysConfigService.FindValueByKey("sys.user.fristPasswdChange")
if parse.Boolean(fristPasswdChangeStr) {
forcePasswdChange = loginCount < 1 || passwordUpdateTime == 0
}
// 非首次登录,判断密码是否过期
if !forcePasswdChange {
alert, err := s.sysUserService.ValidatePasswordExpireTime(passwordUpdateTime)
if err != nil {
return alert, err
}
forcePasswdChange = alert
}
return forcePasswdChange, nil
}
// RoleAndMenuPerms 角色和菜单数据权限
func (s Account) RoleAndMenuPerms(userId int64, isSystemUser bool) ([]string, []string) {
if isSystemUser {
return []string{constants.SYS_ROLE_SYSTEM_KEY}, []string{constants.SYS_PERMISSION_SYSTEM}
}
// 角色key
var roleGroup []string
roles := s.sysRoleService.FindByUserId(userId)
for _, role := range roles {
roleGroup = append(roleGroup, role.RoleKey)
}
// 菜单权限key
perms := s.sysMenuService.FindPermsByUserId(userId)
return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms)
}
// RouteMenus 前端路由所需要的菜单
func (s Account) RouteMenus(userId int64, isSystemUser bool) []systemModelVO.Router {
var buildMenus []systemModelVO.Router
if isSystemUser {
menus := s.sysMenuService.BuildTreeMenusByUserId(0)
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
} else {
menus := s.sysMenuService.BuildTreeMenusByUserId(userId)
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
}
return buildMenus
return s.sysUserService.FindByUserName(username, sType, uid)
}

View File

@@ -0,0 +1,36 @@
package service
import (
"be.ems/src/framework/constants"
"be.ems/src/framework/utils/parse"
systemModelVO "be.ems/src/modules/system/model/vo"
)
// RoleAndMenuPerms 角色和菜单数据权限
func (s Account) RoleAndMenuPerms(userId int64, isSystemUser bool) ([]string, []string) {
if isSystemUser {
return []string{constants.SYS_ROLE_SYSTEM_KEY}, []string{constants.SYS_PERMISSION_SYSTEM}
}
// 角色key
var roleGroup []string
roles := s.sysRoleService.FindByUserId(userId)
for _, role := range roles {
roleGroup = append(roleGroup, role.RoleKey)
}
// 菜单权限key
perms := s.sysMenuService.FindPermsByUserId(userId)
return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms)
}
// RouteMenus 前端路由所需要的菜单
func (s Account) RouteMenus(userId int64, isSystemUser bool) []systemModelVO.Router {
var buildMenus []systemModelVO.Router
if isSystemUser {
menus := s.sysMenuService.BuildTreeMenusByUserId(0)
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
} else {
menus := s.sysMenuService.BuildTreeMenusByUserId(userId)
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
}
return buildMenus
}

View File

@@ -0,0 +1,108 @@
package service
import (
"encoding/json"
"fmt"
"github.com/go-ldap/ldap/v3"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/auth/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// ByLDAP 登录创建用户信息
func (s *Account) ByLDAP(body model.LoginSourceBody) (token.UserInfo, error) {
info := token.UserInfo{}
rows := s.sysLogSourceService.FindByActive(body.UID)
if len(rows) != 1 {
return info, fmt.Errorf("ldap auth source not exist")
}
item := rows[0]
if item.Config == "" {
return info, fmt.Errorf("ldap auth source config is empty")
}
var source systemModelVo.SysLoginSourceLDAP
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return info, err
}
// 校验LDAP用户
err := ldapAuth(source, body.Username, body.Password)
if err != nil {
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(body.Username, item.Type, item.UID)
if sysUser.UserId == 0 || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, body.Username, body.Password)
}
if sysUser.UserId == 0 || sysUser.UserName != body.Username {
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("login.errStatus")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
}
// ldapAuth 校验LDAP用户
func ldapAuth(source systemModelVo.SysLoginSourceLDAP, username, password string) error {
// 连接LDAP
l, err := ldap.DialURL(source.URL)
if err != nil {
return err
}
defer l.Close()
// 绑定DN校验
if source.BindDN != "" && source.BindPassword != "" {
if err := l.Bind(source.BindDN, source.BindPassword); err != nil {
return fmt.Errorf("ldap user bind %s", err)
}
}
// 搜索用户
searchRequest := ldap.NewSearchRequest(
source.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(source.UserFilter, ldap.EscapeFilter(username)),
[]string{"dn", "uid"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
return fmt.Errorf("ldap user search %s", err)
}
// for _, entry := range sr.Entries {
// fmt.Printf("%s ==== %v\n", entry.DN, entry.GetAttributeValue("uid"))
// }
if len(sr.Entries) != 1 {
return fmt.Errorf("ldap user does not exist or too many entries returned")
}
// 校验密码
if err = l.Bind(sr.Entries[0].DN, password); err != nil {
return fmt.Errorf("ldap user bind %s", err)
}
return nil
}

View File

@@ -0,0 +1,168 @@
package service
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"golang.org/x/oauth2"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/auth/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// ByOAuth2CodeURL 获取OAuth2登录URL
func (s *Account) ByOAuth2CodeURL(state string) (string, error) {
rows := s.sysLogSourceService.FindByActive(state)
if len(rows) != 1 {
return "", fmt.Errorf("oauth2 auth source not exist")
}
item := rows[0]
if item.Config == "" {
return "", fmt.Errorf("oauth2 auth source config is empty")
}
var source systemModelVo.SysLoginSourceOAuth2
json.Unmarshal([]byte(item.Config), &source)
conf := oauth2.Config{
ClientID: source.ClientID,
ClientSecret: source.ClientSecret,
RedirectURL: source.RedirectURL,
Scopes: source.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: source.AuthURL,
TokenURL: source.TokenURL,
},
}
return conf.AuthCodeURL(state, oauth2.AccessTypeOffline), nil
}
// ByOAuth2 登录创建用户信息
func (s *Account) ByOAuth2(body model.LoginSourceOauth2Body) (token.UserInfo, error) {
info := token.UserInfo{}
rows := s.sysLogSourceService.FindByActive(body.State)
if len(rows) != 1 {
return info, fmt.Errorf("oauth2 auth source not exist")
}
item := rows[0]
if item.Config == "" {
return info, fmt.Errorf("oauth2 auth source config is empty")
}
var source systemModelVo.SysLoginSourceOAuth2
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return info, err
}
// 校验OAuth2用户
account, err := oauth2Auth(source, body.Code)
if err != nil {
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(account, item.Type, item.UID)
if sysUser.UserId == 0 || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, account, account)
}
if sysUser.UserId == 0 || sysUser.UserName != account {
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("login.errStatus")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
}
// oauth2Auth 校验OAuth2用户
func oauth2Auth(source systemModelVo.SysLoginSourceOAuth2, code string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conf := oauth2.Config{
ClientID: source.ClientID,
ClientSecret: source.ClientSecret,
RedirectURL: source.RedirectURL,
Scopes: source.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: source.AuthURL,
TokenURL: source.TokenURL,
},
}
token, err := conf.Exchange(ctx, code)
if err != nil {
return "", err
}
// 使用token创建HTTP客户端 请求用户信息
resp, err := conf.Client(ctx, token).Get(source.UserURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 解析用户信息
var userInfo map[string]any
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return "", err
}
// 读取嵌套数据
value, found := getValueByPath(userInfo, source.AccountField)
if !found {
return "", fmt.Errorf("oauth2 auth source account field not exist")
}
return fmt.Sprintf("%v", value), nil
}
// getValueByPath 从嵌套的 map[string]any 获取嵌套键对应的值
func getValueByPath(data map[string]any, path string) (any, bool) {
keys := strings.Split(path, ".") // 按照 "." 拆分路径
return getValue(data, keys)
}
// getValue 是递归查找嵌套 map 的函数
func getValue(data map[string]any, keys []string) (any, bool) {
if len(keys) == 0 {
return data, false
}
// 获取当前键
key := keys[0]
// 获取当前键的值
val, ok := data[key]
if !ok {
return nil, false // 找不到键,返回 false
}
// 如果还有嵌套键,继续查找
if len(keys) > 1 {
// 递归查找嵌套 map
if reflect.TypeOf(val).Kind() == reflect.Map {
// 将 `any` 转换为 `map[string]any` 类型
if nestedMap, ok := val.(map[string]any); ok {
return getValue(nestedMap, keys[1:])
}
}
}
return val, true
}

View File

@@ -0,0 +1,88 @@
package service
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/wneessen/go-mail"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/auth/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// BySMTP 登录创建用户信息
func (s *Account) BySMTP(body model.LoginSourceBody) (token.UserInfo, error) {
info := token.UserInfo{}
rows := s.sysLogSourceService.FindByActive(body.UID)
if len(rows) != 1 {
return info, fmt.Errorf("smtp auth source not exist")
}
item := rows[0]
if item.Config == "" {
return info, fmt.Errorf("smtp auth source config is empty")
}
var source systemModelVo.SysLoginSourceSMTP
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return info, err
}
// 校验SMTP用户
err := smtpAuth(source, body.Username, body.Password)
if err != nil {
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(body.Username, item.Type, item.UID)
if sysUser.UserId == 0 || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, body.Username, body.Password)
}
if sysUser.UserId == 0 || sysUser.UserName != body.Username {
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("login.errStatus")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
}
// smtpAuth 校验SMTP用户
func smtpAuth(source systemModelVo.SysLoginSourceSMTP, username, password string) error {
client, err := mail.NewClient(source.Host,
mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover),
mail.WithUsername(username),
mail.WithPort(source.Port),
mail.WithPassword(password),
mail.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
)
if err != nil {
return fmt.Errorf("failed to create mail client %s", err)
}
// 连接到邮件SMTP服务器
if err = client.DialWithContext(context.Background()); err != nil {
return err
}
defer client.Close()
return nil
}

View File

@@ -0,0 +1,138 @@
package service
import (
"fmt"
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/database/redis"
"be.ems/src/framework/token"
"be.ems/src/framework/utils/crypto"
"be.ems/src/framework/utils/parse"
)
// ValidateCaptcha 校验验证码
func (s Account) ValidateCaptcha(code, uuid string) error {
// 验证码检查,从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.FindValueByKey("sys.account.captchaEnabled")
if !parse.Boolean(captchaEnabledStr) {
return nil
}
if code == "" || uuid == "" {
// 验证码信息错误
return fmt.Errorf("captcha.err")
}
verifyKey := constants.CACHE_CAPTCHA_CODE + ":" + uuid
captcha, _ := redis.Get("", verifyKey)
if captcha == "" {
// 验证码已失效
return fmt.Errorf("captcha.errValid")
}
_ = redis.Del("", verifyKey)
if captcha != code {
// 验证码错误
return fmt.Errorf("captcha.err")
}
return nil
}
// ByUsername 登录创建用户信息
func (s Account) ByUsername(username, password string) (token.UserInfo, error) {
info := token.UserInfo{}
// 检查密码重试次数
retryKey, retryCount, lockTime, err := s.passwordRetryCount(username)
if err != nil {
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(username, "System", "#")
if sysUser.UserName != username {
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("login.errStatus")
}
// 检验用户密码
compareBool := crypto.BcryptCompare(password, sysUser.Password)
if compareBool {
s.CleanLoginRecordCache(sysUser.UserName) // 清除错误记录次数
} else {
_ = redis.Set("", retryKey, retryCount+1, lockTime)
return info, fmt.Errorf("login.errNameOrPasswd")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
}
// CleanLoginRecordCache 清除错误记录次数
func (s Account) CleanLoginRecordCache(userName string) bool {
cacheKey := fmt.Sprintf("%s:%s", constants.CACHE_PWD_ERR_COUNT, userName)
hasKey, err := redis.Has("", cacheKey)
if hasKey > 0 && err == nil {
return redis.Del("", cacheKey) == nil
}
return false
}
// passwordRetryCount 密码重试次数
func (s Account) passwordRetryCount(userName string) (string, int64, time.Duration, error) {
// 从数据库配置获取登录次数和错误锁定时间
maxRetryCountStr := s.sysConfigService.FindValueByKey("sys.user.maxRetryCount")
lockTimeStr := s.sysConfigService.FindValueByKey("sys.user.lockTime")
// 验证登录次数和错误锁定时间
maxRetryCount := parse.Number(maxRetryCountStr)
lockTime := parse.Number(lockTimeStr)
// 验证缓存记录次数
retryKey := fmt.Sprintf("%s:%s", constants.CACHE_PWD_ERR_COUNT, userName)
retryCount, err := redis.Get("", retryKey)
if retryCount == "" || err != nil {
retryCount = "0"
}
// 是否超过错误值
retryCountInt64 := parse.Number(retryCount)
if retryCountInt64 >= int64(maxRetryCount) {
// msg := fmt.Sprintf("密码输入错误 %d 次,帐户锁定 %d 分钟", maxRetryCount, lockTime)
msg := fmt.Errorf("login.errRetryPasswd") // 密码输入错误多次,帐户已被锁定
return retryKey, retryCountInt64, time.Duration(lockTime) * time.Minute, fmt.Errorf("%s", msg)
}
return retryKey, retryCountInt64, time.Duration(lockTime) * time.Minute, nil
}
// PasswordCountOrExpireTime 首次登录或密码过期时间
func (s Account) PasswordCountOrExpireTime(loginCount, passwordUpdateTime int64) (bool, error) {
forcePasswdChange := false
// 从数据库配置获取-首次登录密码修改
fristPasswdChangeStr := s.sysConfigService.FindValueByKey("sys.user.fristPasswdChange")
if parse.Boolean(fristPasswdChangeStr) {
forcePasswdChange = loginCount < 1 || passwordUpdateTime == 0
}
// 非首次登录,判断密码是否过期
if !forcePasswdChange {
alert, err := s.sysUserService.ValidatePasswordExpireTime(passwordUpdateTime)
if err != nil {
return alert, err
}
forcePasswdChange = alert
}
return forcePasswdChange, nil
}

View File

@@ -61,12 +61,14 @@ func (s Register) ByUserName(username, password string) (int64, error) {
sysUser := systemModel.SysUser{
UserName: username,
NickName: username, // 昵称使用名称账号
Password: password, // 原始密码
NickName: username, // 昵称使用名称账号
Password: password, // 原始密码
UserType: "System",
UserSource: "#",
Sex: "0", // 性别未选择
StatusFlag: constants.STATUS_YES, // 账号状态激活
DeptId: 100, // 归属部门为根节点
CreateBy: "register", // 创建来源
DeptId: 101, // 归属部门为根节点
CreateBy: "System", // 创建来源
}
// 新增用户的角色管理