feat: 客户端oauth2认证模块
This commit is contained in:
@@ -116,4 +116,62 @@ func Setup(router *gin.Engine) {
|
||||
)
|
||||
}
|
||||
|
||||
// 客户端授权管理
|
||||
oauth2Client := controller.NewOauth2Client
|
||||
oauth2ClientGroup := router.Group("/oauth2/client")
|
||||
{
|
||||
oauth2ClientGroup.GET("/list",
|
||||
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
|
||||
oauth2Client.List,
|
||||
)
|
||||
oauth2ClientGroup.GET("/:clientId",
|
||||
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
|
||||
oauth2Client.Info,
|
||||
)
|
||||
oauth2ClientGroup.POST("",
|
||||
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
|
||||
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_INSERT)),
|
||||
oauth2Client.Add,
|
||||
)
|
||||
oauth2ClientGroup.PUT("",
|
||||
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
|
||||
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_UPDATE)),
|
||||
oauth2Client.Edit,
|
||||
)
|
||||
oauth2ClientGroup.DELETE("/:id",
|
||||
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
|
||||
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_DELETE)),
|
||||
oauth2Client.Remove,
|
||||
)
|
||||
}
|
||||
|
||||
// 授权认证
|
||||
oauth2 := controller.NewOauth2
|
||||
oauth2Group := router.Group("/oauth2")
|
||||
{
|
||||
oauth2Group.GET("/authorize",
|
||||
middleware.RateLimit(middleware.LimitOption{
|
||||
Time: 60,
|
||||
Count: 30,
|
||||
Type: middleware.LIMIT_IP,
|
||||
}),
|
||||
oauth2.Authorize,
|
||||
)
|
||||
oauth2Group.POST("/token",
|
||||
middleware.RateLimit(middleware.LimitOption{
|
||||
Time: 180,
|
||||
Count: 15,
|
||||
Type: middleware.LIMIT_IP,
|
||||
}),
|
||||
oauth2.Token,
|
||||
)
|
||||
oauth2Group.POST("/refresh-token",
|
||||
middleware.RateLimit(middleware.LimitOption{
|
||||
Time: 60,
|
||||
Count: 5,
|
||||
Type: middleware.LIMIT_IP,
|
||||
}),
|
||||
oauth2.RefreshToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
215
src/modules/auth/controller/oauth2.go
Normal file
215
src/modules/auth/controller/oauth2.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/token"
|
||||
"be.ems/src/modules/auth/model"
|
||||
"be.ems/src/modules/auth/service"
|
||||
)
|
||||
|
||||
// NewOauth2 实例化控制层
|
||||
var NewOauth2 = &Oauth2Controller{
|
||||
oauth2Service: service.NewOauth2Service,
|
||||
oauth2ClientService: service.NewOauth2ClientService,
|
||||
oauth2LogLoginService: service.NewOauth2LogLogin,
|
||||
}
|
||||
|
||||
// Oauth2Controller 授权第三方客户端应用认证 控制层处理
|
||||
//
|
||||
// PATH /oauth2
|
||||
type Oauth2Controller struct {
|
||||
oauth2Service *service.Oauth2Service // 用户授权第三方信息服务
|
||||
oauth2ClientService *service.Oauth2ClientService // 用户授权第三方应用信息服务
|
||||
oauth2LogLoginService *service.Oauth2LogLoginService // 用户授权第三方应用登录日志
|
||||
}
|
||||
|
||||
// Authorize 获取登录预授权码
|
||||
//
|
||||
// GET /authorize
|
||||
func (s Oauth2Controller) Authorize(c *gin.Context) {
|
||||
var query model.CodeQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
// 是否存在clientId
|
||||
info := s.oauth2ClientService.FindByClientId(query.ClientId)
|
||||
if info.ClientId == "" || info.ClientId != query.ClientId {
|
||||
c.JSON(200, resp.ErrMsg("clientId not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
// 判断IP白名单
|
||||
if !strings.Contains(info.IPWhite, c.ClientIP()) {
|
||||
c.JSON(200, resp.ErrMsg("ip whitelist mismatch"))
|
||||
return
|
||||
}
|
||||
|
||||
// 生成登录预授权码
|
||||
code := s.oauth2Service.CreateCode()
|
||||
|
||||
redirectURL := fmt.Sprintf("%s?code=%s&state=%s", query.RedirectUrl, code, query.State)
|
||||
c.Redirect(302, redirectURL)
|
||||
}
|
||||
|
||||
// Tooken 通过授权码获取访问令牌
|
||||
//
|
||||
// POST /token
|
||||
func (s Oauth2Controller) Token(c *gin.Context) {
|
||||
var body model.TokenBody
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
|
||||
return
|
||||
}
|
||||
if body.GrantType != "authorization_code" || body.Code == "" {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "grantType or code error"))
|
||||
return
|
||||
}
|
||||
|
||||
// 当前请求信息
|
||||
ipaddr, location := reqctx.IPAddrLocation(c)
|
||||
os, browser := reqctx.UaOsBrowser(c)
|
||||
|
||||
// 校验验证码 根据错误信息,创建系统访问记录
|
||||
if err := s.oauth2Service.ValidateCode(body.Code); err != nil {
|
||||
msg := fmt.Sprintf("%s code %s", err.Error(), body.Code)
|
||||
s.oauth2LogLoginService.Insert(
|
||||
body.ClientId, constants.STATUS_NO, msg,
|
||||
[4]string{ipaddr, location, os, browser},
|
||||
)
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 登录客户端信息
|
||||
info, err := s.oauth2Service.ByClient(body.ClientId, body.ClientSecret, ipaddr)
|
||||
if err != nil {
|
||||
s.oauth2LogLoginService.Insert(
|
||||
body.ClientId, constants.STATUS_NO, err.Error(),
|
||||
[4]string{ipaddr, location, os, browser},
|
||||
)
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
deviceFingerprint := reqctx.DeviceFingerprint(c, info.ClientId)
|
||||
|
||||
// 生成访问令牌
|
||||
accessToken, expiresIn := token.Oauth2TokenCreate(info.ClientId, deviceFingerprint, "access")
|
||||
if accessToken == "" || expiresIn == 0 {
|
||||
c.JSON(200, resp.ErrMsg("token generation failed"))
|
||||
return
|
||||
}
|
||||
// 生成刷新令牌
|
||||
refreshToken, refreshExpiresIn := token.Oauth2TokenCreate(info.ClientId, deviceFingerprint, "refresh")
|
||||
|
||||
// 记录令牌,创建系统访问记录
|
||||
token.Oauth2InfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
|
||||
s.oauth2Service.UpdateLoginDateAndIP(info)
|
||||
s.oauth2LogLoginService.Insert(
|
||||
body.ClientId, constants.STATUS_YES, "Authorization successful",
|
||||
[4]string{ipaddr, location, os, browser},
|
||||
)
|
||||
|
||||
c.JSON(200, resp.OkData(map[string]any{
|
||||
"tokenType": constants.HEADER_PREFIX,
|
||||
"accessToken": accessToken,
|
||||
"expiresIn": expiresIn,
|
||||
"refreshToken": refreshToken,
|
||||
"refreshExpiresIn": refreshExpiresIn,
|
||||
}))
|
||||
}
|
||||
|
||||
// RefreshToken 通过刷新令牌续期访问令牌
|
||||
//
|
||||
// POST /refresh-token
|
||||
func (s Oauth2Controller) RefreshToken(c *gin.Context) {
|
||||
var body model.TokenBody
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
|
||||
return
|
||||
}
|
||||
if body.GrantType != "refresh_token" || body.RefreshToken == "" {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "grantType or refreshToken error"))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证刷新令牌是否有效
|
||||
claims, err := token.Oauth2TokenVerify(body.RefreshToken, "refresh")
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_INVALID, err.Error()))
|
||||
return
|
||||
}
|
||||
clientId := fmt.Sprint(claims[constants.JWT_CLIENT_ID])
|
||||
|
||||
// 当前请求信息
|
||||
ipaddr, location := reqctx.IPAddrLocation(c)
|
||||
os, browser := reqctx.UaOsBrowser(c)
|
||||
|
||||
// 客户端信息
|
||||
info, err := s.oauth2Service.ByClient(body.ClientId, body.ClientSecret, ipaddr)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 客户端ID是否一致
|
||||
if clientId != body.ClientId {
|
||||
c.JSON(200, resp.ErrMsg("clientId mismatch"))
|
||||
return
|
||||
}
|
||||
// 设备指纹信息是否一致
|
||||
deviceId := fmt.Sprint(claims[constants.JWT_DEVICE_ID])
|
||||
deviceFingerprint := reqctx.DeviceFingerprint(c, clientId)
|
||||
if deviceId != deviceFingerprint {
|
||||
c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_DEVICE, resp.MSG_AUTH_DEVICE))
|
||||
return
|
||||
}
|
||||
|
||||
// 生成访问令牌
|
||||
accessToken, expiresIn := token.Oauth2TokenCreate(clientId, deviceFingerprint, "access")
|
||||
if accessToken == "" || expiresIn == 0 {
|
||||
c.JSON(200, resp.ErrMsg("token generation failed"))
|
||||
return
|
||||
}
|
||||
// 生成刷新令牌
|
||||
now := time.Now()
|
||||
exp, _ := claims.GetExpirationTime()
|
||||
iat, _ := claims.GetIssuedAt()
|
||||
refreshExpiresIn := int64(exp.Sub(now).Seconds())
|
||||
refreshToken := body.RefreshToken
|
||||
|
||||
// 如果当前时间大于过期时间的一半,则生成新令牌
|
||||
halfExp := exp.Add(-(exp.Sub(iat.Time)) / 2)
|
||||
if now.After(halfExp) {
|
||||
refreshToken, refreshExpiresIn = token.Oauth2TokenCreate(clientId, deviceFingerprint, "refresh")
|
||||
}
|
||||
|
||||
// 记录令牌,创建系统访问记录
|
||||
token.Oauth2InfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
|
||||
s.oauth2Service.UpdateLoginDateAndIP(info)
|
||||
s.oauth2LogLoginService.Insert(
|
||||
info.ClientId, constants.STATUS_YES, "Refresh Access Token Succeeded",
|
||||
[4]string{ipaddr, location, os, browser},
|
||||
)
|
||||
|
||||
// 返回访问令牌和刷新令牌
|
||||
c.JSON(200, resp.OkData(map[string]any{
|
||||
"tokenType": constants.HEADER_PREFIX,
|
||||
"accessToken": accessToken,
|
||||
"expiresIn": expiresIn,
|
||||
"refreshToken": refreshToken,
|
||||
"refreshExpiresIn": refreshExpiresIn,
|
||||
}))
|
||||
}
|
||||
168
src/modules/auth/controller/oauth2_client.go
Normal file
168
src/modules/auth/controller/oauth2_client.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"be.ems/src/framework/i18n"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/utils/parse"
|
||||
"be.ems/src/modules/auth/model"
|
||||
"be.ems/src/modules/auth/service"
|
||||
)
|
||||
|
||||
// NewOauth2Client 实例化控制层
|
||||
var NewOauth2Client = &Oauth2ClientController{
|
||||
oauth2ClientService: service.NewOauth2ClientService,
|
||||
}
|
||||
|
||||
// Oauth2ClientController 客户端授权管理 控制层处理
|
||||
//
|
||||
// PATH /oauth2/client
|
||||
type Oauth2ClientController struct {
|
||||
oauth2ClientService *service.Oauth2ClientService // 用户授权第三方应用信息服务
|
||||
}
|
||||
|
||||
// List 列表
|
||||
//
|
||||
// GET /list
|
||||
func (s Oauth2ClientController) List(c *gin.Context) {
|
||||
query := reqctx.QueryMap(c)
|
||||
rows, total := s.oauth2ClientService.FindByPage(query)
|
||||
c.JSON(200, resp.OkData(map[string]any{"rows": rows, "total": total}))
|
||||
}
|
||||
|
||||
// Info 信息
|
||||
//
|
||||
// GET /:clientId
|
||||
func (s Oauth2ClientController) Info(c *gin.Context) {
|
||||
clientId := c.Param("clientId")
|
||||
if clientId == "" {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: clientId is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
info := s.oauth2ClientService.FindByClientId(clientId)
|
||||
if info.ClientId == clientId {
|
||||
c.JSON(200, resp.OkData(info))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.ErrMsg("clientId does not exist"))
|
||||
}
|
||||
|
||||
// Add 新增
|
||||
//
|
||||
// POST /
|
||||
func (s Oauth2ClientController) Add(c *gin.Context) {
|
||||
var body model.Oauth2Client
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
|
||||
return
|
||||
}
|
||||
if body.Id > 0 {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id not is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
// 本地IP地址不支持
|
||||
localHosts := []string{"127.0.0.1", "localhost", "::ffff:", "::1"}
|
||||
localHost := false
|
||||
for _, host := range localHosts {
|
||||
if strings.Contains(body.IPWhite, host) {
|
||||
localHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if localHost {
|
||||
c.JSON(200, resp.ErrMsg("no support local host"))
|
||||
return
|
||||
}
|
||||
|
||||
body.CreateBy = reqctx.LoginUserToUserName(c)
|
||||
insertId := s.oauth2ClientService.Insert(body)
|
||||
if insertId > 0 {
|
||||
c.JSON(200, resp.OkData(insertId))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.Err(nil))
|
||||
}
|
||||
|
||||
// Edit 更新
|
||||
//
|
||||
// PUT /
|
||||
func (s Oauth2ClientController) Edit(c *gin.Context) {
|
||||
var body model.Oauth2Client
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
|
||||
return
|
||||
}
|
||||
if body.Id <= 0 {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
// 本地IP地址不支持
|
||||
localHosts := []string{"127.0.0.1", "localhost", "::ffff:", "::1"}
|
||||
localHost := false
|
||||
for _, host := range localHosts {
|
||||
if strings.Contains(body.IPWhite, host) {
|
||||
localHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if localHost {
|
||||
c.JSON(200, resp.ErrMsg("no support local host"))
|
||||
return
|
||||
}
|
||||
|
||||
// 查询信息
|
||||
info := s.oauth2ClientService.FindById(body.Id)
|
||||
if info.ClientId == "" || info.Id != body.Id {
|
||||
c.JSON(200, resp.ErrMsg("modification failed, data not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
info.Title = body.Title
|
||||
info.IPWhite = body.IPWhite
|
||||
info.Remark = body.Remark
|
||||
info.UpdateBy = reqctx.LoginUserToUserName(c)
|
||||
rowsAffected := s.oauth2ClientService.Update(info)
|
||||
if rowsAffected > 0 {
|
||||
c.JSON(200, resp.Ok(nil))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.Err(nil))
|
||||
}
|
||||
|
||||
// Remove 删除
|
||||
//
|
||||
// DELETE /:id
|
||||
func (s Oauth2ClientController) Remove(c *gin.Context) {
|
||||
language := reqctx.AcceptLanguage(c)
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
// 处理字符转id数组后去重
|
||||
uniqueIDs := parse.RemoveDuplicatesToArray(id, ",")
|
||||
// 转换成int64数组类型
|
||||
ids := make([]int64, 0)
|
||||
for _, v := range uniqueIDs {
|
||||
ids = append(ids, parse.Number(v))
|
||||
}
|
||||
|
||||
rows, err := s.oauth2ClientService.DeleteByIds(ids)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
|
||||
c.JSON(200, resp.OkMsg(msg))
|
||||
}
|
||||
17
src/modules/auth/model/oauth2_body.go
Normal file
17
src/modules/auth/model/oauth2_body.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
// TokenBody 获取访问令牌参数
|
||||
type TokenBody struct {
|
||||
ClientId string `json:"clientId" binding:"required"` // 申请应用时获得的client_id
|
||||
ClientSecret string `json:"clientSecret" binding:"required"` // 申请应用时分配的secret
|
||||
GrantType string `json:"grantType" binding:"required,oneof=authorization_code refresh_token"` // 请求的类型,此处的值固定为 authorization_code/refresh_token
|
||||
Code string `json:"code"` // 授权拿到的code值
|
||||
RefreshToken string `json:"refreshToken"` // 刷新令牌
|
||||
}
|
||||
|
||||
// CodeQuery 重定向授权码参数
|
||||
type CodeQuery struct {
|
||||
RedirectUrl string `form:"redirectUrl" binding:"required"` // 授权回调地址
|
||||
ClientId string `form:"clientId" binding:"required"` // 申请得到的客户端ID
|
||||
State string `form:"state" binding:"required"` // 随机字符串,认证服务器会原封不动地返回这个值
|
||||
}
|
||||
22
src/modules/auth/model/oauth2_client.go
Normal file
22
src/modules/auth/model/oauth2_client.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
// Oauth2Client 用户授权第三方应用表 oauth2_client
|
||||
type Oauth2Client struct {
|
||||
Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 应用ID
|
||||
ClientId string `gorm:"column:client_id" json:"clientId"` // 应用的唯一标识
|
||||
ClientSecret string `gorm:"column:client_secret" json:"clientSecret"` // 应用的凭证秘钥
|
||||
Title string `gorm:"column:title" json:"title"` // 应用名称
|
||||
IPWhite string `gorm:"column:ip_white" json:"ipWhite"` // IP白名单
|
||||
DelFlag string `gorm:"column:del_flag" json:"delFlag"` // 删除标记(0存在 1删除)
|
||||
LoginIp string `gorm:"column:login_ip" json:"loginIp"` // 最后登录IP
|
||||
LoginTime int64 `gorm:"column:login_time" json:"loginTime"` // 最后登录时间
|
||||
CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建者
|
||||
CreateTime int64 `gorm:"column:create_time" json:"createTime"` // 创建时间
|
||||
UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新者
|
||||
UpdateTime int64 `gorm:"column:update_time" json:"updateTime"` // 更新时间
|
||||
Remark string `gorm:"column:remark" json:"remark"` // 备注
|
||||
}
|
||||
|
||||
func (*Oauth2Client) TableName() string {
|
||||
return "oauth2_client"
|
||||
}
|
||||
19
src/modules/auth/model/oauth2_log_login.go
Normal file
19
src/modules/auth/model/oauth2_log_login.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package model
|
||||
|
||||
// Oauth2LogLogin 用户授权第三方应用登录日志表
|
||||
type Oauth2LogLogin struct {
|
||||
ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 登录ID
|
||||
ClientId string `json:"clientId" gorm:"column:client_id"` // 应用的唯一标识
|
||||
LoginIp string `json:"loginIp" gorm:"column:login_ip"` // 登录IP地址
|
||||
LoginLocation string `json:"loginLocation" gorm:"column:login_location"` // 登录地点
|
||||
Browser string `json:"browser" gorm:"column:browser"` // 浏览器类型
|
||||
OS string `json:"os" gorm:"column:os"` // 操作系统
|
||||
StatusFlag string `json:"statusFlag" gorm:"column:status_flag"` // 登录状态(0失败 1成功)
|
||||
Msg string `json:"msg" gorm:"column:msg"` // 提示消息
|
||||
LoginTime int64 `json:"loginTime" gorm:"column:login_time"` // 登录时间
|
||||
}
|
||||
|
||||
// TableName 表名称
|
||||
func (*Oauth2LogLogin) TableName() string {
|
||||
return "oauth2_log_login"
|
||||
}
|
||||
168
src/modules/auth/repository/oauth2_client.go
Normal file
168
src/modules/auth/repository/oauth2_client.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/database/db"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/modules/auth/model"
|
||||
)
|
||||
|
||||
// NewOauth2Client 实例化数据层
|
||||
var NewOauth2Client = &Oauth2Client{}
|
||||
|
||||
// Oauth2Client 用户授权第三方应用表 数据层处理
|
||||
type Oauth2Client struct{}
|
||||
|
||||
// SelectByPage 分页查询集合
|
||||
func (r Oauth2Client) SelectByPage(query map[string]string) ([]model.Oauth2Client, int64) {
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
tx = tx.Where("del_flag = 0")
|
||||
// 查询条件拼接
|
||||
if v, ok := query["clientId"]; ok && v != "" {
|
||||
tx = tx.Where("client_id = ?", v)
|
||||
}
|
||||
if v, ok := query["title"]; ok && v != "" {
|
||||
tx = tx.Where("title like ?", v+"%")
|
||||
}
|
||||
if v, ok := query["beginTime"]; ok && v != "" {
|
||||
if len(v) == 10 {
|
||||
v = fmt.Sprintf("%s000", v)
|
||||
tx = tx.Where("login_time >= ?", v)
|
||||
} else if len(v) == 13 {
|
||||
tx = tx.Where("login_time >= ?", v)
|
||||
}
|
||||
}
|
||||
if v, ok := query["endTime"]; ok && v != "" {
|
||||
if len(v) == 10 {
|
||||
v = fmt.Sprintf("%s999", v)
|
||||
tx = tx.Where("login_time <= ?", v)
|
||||
} else if len(v) == 13 {
|
||||
tx = tx.Where("login_time <= ?", v)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询结果
|
||||
var total int64 = 0
|
||||
rows := []model.Oauth2Client{}
|
||||
|
||||
// 查询数量为0直接返回
|
||||
if err := tx.Count(&total).Error; err != nil || total <= 0 {
|
||||
return rows, total
|
||||
}
|
||||
|
||||
// 查询数据分页
|
||||
pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"])
|
||||
tx = tx.Limit(pageSize).Offset(pageSize * pageNum)
|
||||
err := tx.Find(&rows).Error
|
||||
if err != nil {
|
||||
return rows, total
|
||||
}
|
||||
return rows, total
|
||||
}
|
||||
|
||||
// Select 查询集合
|
||||
func (r Oauth2Client) Select(param model.Oauth2Client) []model.Oauth2Client {
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
// 查询条件拼接
|
||||
if param.ClientId != "" {
|
||||
tx = tx.Where("client_id = ?", param.ClientId)
|
||||
}
|
||||
if param.Title != "" {
|
||||
tx = tx.Where("title like ?", param.Title+"%")
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
rows := []model.Oauth2Client{}
|
||||
if err := tx.Find(&rows).Error; err != nil {
|
||||
return rows
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// SelectByIds 通过ID查询信息
|
||||
func (r Oauth2Client) SelectByIds(ids []int64) []model.Oauth2Client {
|
||||
rows := []model.Oauth2Client{}
|
||||
if len(ids) <= 0 {
|
||||
return rows
|
||||
}
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
// 构建查询条件
|
||||
tx = tx.Where("id in ? and del_flag = 0", ids)
|
||||
// 查询数据
|
||||
if err := tx.Find(&rows).Error; err != nil {
|
||||
logger.Errorf("query find err => %v", err.Error())
|
||||
return rows
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// Insert 新增信息 返回新增数据ID
|
||||
func (r Oauth2Client) Insert(param model.Oauth2Client) int64 {
|
||||
if param.CreateBy != "" {
|
||||
ms := time.Now().UnixMilli()
|
||||
param.UpdateBy = param.CreateBy
|
||||
param.UpdateTime = ms
|
||||
param.CreateTime = ms
|
||||
param.DelFlag = "0"
|
||||
}
|
||||
// 执行插入
|
||||
if err := db.DB("").Create(¶m).Error; err != nil {
|
||||
logger.Errorf("insert err => %v", err.Error())
|
||||
return 0
|
||||
}
|
||||
return param.Id
|
||||
}
|
||||
|
||||
// Update 修改信息 返回受影响行数
|
||||
func (r Oauth2Client) Update(param model.Oauth2Client) int64 {
|
||||
if param.Id <= 0 {
|
||||
return 0
|
||||
}
|
||||
if param.UpdateBy != "" {
|
||||
param.UpdateTime = time.Now().UnixMilli()
|
||||
}
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
// 构建查询条件
|
||||
tx = tx.Where("id = ?", param.Id)
|
||||
tx = tx.Omit("id", "del_flag", "create_by", "create_time")
|
||||
// 执行更新
|
||||
if err := tx.Updates(param).Error; err != nil {
|
||||
logger.Errorf("update err => %v", err.Error())
|
||||
return 0
|
||||
}
|
||||
return tx.RowsAffected
|
||||
}
|
||||
|
||||
// DeleteByIds 批量删除信息 返回受影响行数
|
||||
func (r Oauth2Client) DeleteByIds(ids []int64) int64 {
|
||||
if len(ids) <= 0 {
|
||||
return 0
|
||||
}
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
// 构建查询条件
|
||||
tx = tx.Where("id in ?", ids)
|
||||
// 执行更新删除标记
|
||||
if err := tx.Update("del_flag", "1").Error; err != nil {
|
||||
logger.Errorf("update err => %v", err.Error())
|
||||
return 0
|
||||
}
|
||||
return tx.RowsAffected
|
||||
}
|
||||
|
||||
// SelectByClientId 通过clientId查询
|
||||
func (r Oauth2Client) SelectByClientId(clientId string) model.Oauth2Client {
|
||||
item := model.Oauth2Client{}
|
||||
if clientId == "" {
|
||||
return item
|
||||
}
|
||||
tx := db.DB("").Model(&model.Oauth2Client{})
|
||||
// 构建查询条件
|
||||
tx = tx.Where("client_id = ? and del_flag = '0'", clientId)
|
||||
// 查询数据
|
||||
if err := tx.Find(&item).Error; err != nil {
|
||||
return item
|
||||
}
|
||||
return item
|
||||
}
|
||||
118
src/modules/auth/repository/oauth2_log_login.go
Normal file
118
src/modules/auth/repository/oauth2_log_login.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/database/db"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/modules/auth/model"
|
||||
)
|
||||
|
||||
// NewOauth2LogLogin 实例化数据层
|
||||
var NewOauth2LogLogin = &Oauth2LogLoginRepository{}
|
||||
|
||||
// Oauth2LogLoginRepository 用户授权第三方应用登录日志表 数据层处理
|
||||
type Oauth2LogLoginRepository struct{}
|
||||
|
||||
// SelectByPage 分页查询集合
|
||||
func (r Oauth2LogLoginRepository) SelectByPage(query map[string]string) ([]model.Oauth2LogLogin, int64) {
|
||||
tx := db.DB("").Model(&model.Oauth2LogLogin{})
|
||||
// 查询条件拼接
|
||||
if v, ok := query["loginIp"]; ok && v != "" {
|
||||
tx = tx.Where("login_ip like ?", v+"%")
|
||||
}
|
||||
if v, ok := query["clientId"]; ok && v != "" {
|
||||
tx = tx.Where("client_id = ?", v)
|
||||
}
|
||||
if v, ok := query["statusFlag"]; ok && v != "" {
|
||||
tx = tx.Where("status_flag = ?", v)
|
||||
}
|
||||
if v, ok := query["beginTime"]; ok && v != "" {
|
||||
if len(v) == 10 {
|
||||
v = fmt.Sprintf("%s000", v)
|
||||
tx = tx.Where("login_time >= ?", v)
|
||||
} else if len(v) == 13 {
|
||||
tx = tx.Where("login_time >= ?", v)
|
||||
}
|
||||
}
|
||||
if v, ok := query["endTime"]; ok && v != "" {
|
||||
if len(v) == 10 {
|
||||
v = fmt.Sprintf("%s999", v)
|
||||
tx = tx.Where("login_time <= ?", v)
|
||||
} else if len(v) == 13 {
|
||||
tx = tx.Where("login_time <= ?", v)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询结果
|
||||
var total int64 = 0
|
||||
rows := []model.Oauth2LogLogin{}
|
||||
|
||||
// 查询数量为0直接返回
|
||||
if err := tx.Count(&total).Error; err != nil || total <= 0 {
|
||||
return rows, total
|
||||
}
|
||||
|
||||
// 查询数据分页
|
||||
pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"])
|
||||
tx = tx.Limit(pageSize).Offset(pageSize * pageNum)
|
||||
// 排序
|
||||
sortByStr, sortOk := query["sortBy"]
|
||||
sortOrderStr, orderOk := query["sortOrder"]
|
||||
if sortOk && sortByStr != "" && orderOk && sortOrderStr != "" {
|
||||
sortByArr := strings.Split(sortByStr, ",")
|
||||
sortOrderArr := strings.Split(sortOrderStr, ",")
|
||||
for i := range sortByArr {
|
||||
sortBy := sortByArr[i]
|
||||
sortOrder := sortOrderArr[i]
|
||||
// 排序字段
|
||||
sort := "id"
|
||||
switch sortBy {
|
||||
case "loginIp":
|
||||
sort = "login_ip"
|
||||
case "createTime":
|
||||
sort = "create_time"
|
||||
}
|
||||
// 排序方式
|
||||
order := "ASC"
|
||||
if strings.HasPrefix(sortOrder, "asc") {
|
||||
order = "ASC"
|
||||
} else if strings.HasPrefix(sortOrder, "desc") {
|
||||
order = "DESC"
|
||||
}
|
||||
tx = tx.Order(fmt.Sprintf("%s %s", sort, order))
|
||||
}
|
||||
} else {
|
||||
tx = tx.Order("id desc")
|
||||
}
|
||||
// 查询数据
|
||||
err := tx.Find(&rows).Error
|
||||
if err != nil {
|
||||
logger.Errorf("query find err => %v", err.Error())
|
||||
return rows, total
|
||||
}
|
||||
return rows, total
|
||||
}
|
||||
|
||||
// Insert 新增信息 返回新增的数据ID
|
||||
func (r Oauth2LogLoginRepository) Insert(param model.Oauth2LogLogin) int64 {
|
||||
param.LoginTime = time.Now().UnixMilli()
|
||||
// 执行插入
|
||||
if err := db.DB("").Create(¶m).Error; err != nil {
|
||||
logger.Errorf("insert err => %v", err.Error())
|
||||
return 0
|
||||
}
|
||||
return param.ID
|
||||
}
|
||||
|
||||
// Clean 清空信息
|
||||
func (r Oauth2LogLoginRepository) Clean() int64 {
|
||||
tx := db.DB("").Delete(&model.Oauth2LogLogin{})
|
||||
if err := tx.Error; err != nil {
|
||||
logger.Errorf("delete err => %v", err.Error())
|
||||
return 0
|
||||
}
|
||||
return tx.RowsAffected
|
||||
}
|
||||
90
src/modules/auth/service/oauth2.go
Normal file
90
src/modules/auth/service/oauth2.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/generate"
|
||||
"be.ems/src/modules/auth/model"
|
||||
"be.ems/src/modules/auth/repository"
|
||||
)
|
||||
|
||||
// NewOauth2Service 实例化服务层
|
||||
var NewOauth2Service = &Oauth2Service{
|
||||
oauth2ClientRepository: repository.NewOauth2Client,
|
||||
}
|
||||
|
||||
// Oauth2Service 用户授权第三方应用信息 服务层处理
|
||||
type Oauth2Service struct {
|
||||
oauth2ClientRepository *repository.Oauth2Client // 用户授权第三方应用表
|
||||
}
|
||||
|
||||
// CreateCode 创建授权码
|
||||
func (s Oauth2Service) CreateCode() string {
|
||||
code := generate.Code(8)
|
||||
uuid := crypto.MD5(code)
|
||||
verifyKey := constants.CACHE_OAUTH2_CODE + ":" + uuid
|
||||
// 授权码有效期,单位秒
|
||||
codeExpiration := 2 * 60 * time.Second
|
||||
_ = redis.Set("", verifyKey, code, codeExpiration)
|
||||
return code
|
||||
}
|
||||
|
||||
// ValidateCode 校验授权码
|
||||
func (s Oauth2Service) ValidateCode(code string) error {
|
||||
if len(code) > 16 {
|
||||
return fmt.Errorf("code length error")
|
||||
}
|
||||
uuid := crypto.MD5(code)
|
||||
verifyKey := constants.CACHE_OAUTH2_CODE + ":" + uuid
|
||||
captcha, _ := redis.Get("", verifyKey)
|
||||
if captcha == "" {
|
||||
return fmt.Errorf("code expire")
|
||||
}
|
||||
_ = redis.Del("", verifyKey)
|
||||
if captcha != strings.ToLower(code) {
|
||||
return fmt.Errorf("code error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ByClient 客户端信息
|
||||
func (s Oauth2Service) ByClient(clientId, clientSecret, ipaddr string) (token.Oauth2Info, error) {
|
||||
info := token.Oauth2Info{}
|
||||
|
||||
// 查询用户登录账号
|
||||
var item model.Oauth2Client
|
||||
rows := s.oauth2ClientRepository.Select(model.Oauth2Client{
|
||||
ClientId: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
})
|
||||
if len(rows) > 0 {
|
||||
item = rows[0]
|
||||
}
|
||||
if item.ClientId == "" || item.ClientSecret == "" {
|
||||
return info, fmt.Errorf("clientId or clientSecret is not exist")
|
||||
}
|
||||
// 判断IP白名单
|
||||
if !strings.Contains(item.IPWhite, ipaddr) {
|
||||
return info, fmt.Errorf("ip whitelist mismatch")
|
||||
}
|
||||
|
||||
info.ClientId = clientId
|
||||
// 用户权限组标识
|
||||
info.Scope = []string{}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpdateLoginDateAndIP 更新登录时间和IP
|
||||
func (s Oauth2Service) UpdateLoginDateAndIP(info token.Oauth2Info) bool {
|
||||
item := s.oauth2ClientRepository.SelectByClientId(info.ClientId)
|
||||
item.LoginIp = info.LoginIp
|
||||
item.LoginTime = info.LoginTime
|
||||
rows := s.oauth2ClientRepository.Update(item)
|
||||
return rows > 0
|
||||
}
|
||||
65
src/modules/auth/service/oauth2_client.go
Normal file
65
src/modules/auth/service/oauth2_client.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"be.ems/src/framework/utils/generate"
|
||||
"be.ems/src/modules/auth/model"
|
||||
"be.ems/src/modules/auth/repository"
|
||||
)
|
||||
|
||||
// NewOauth2ClientService 实例化服务层
|
||||
var NewOauth2ClientService = &Oauth2ClientService{
|
||||
oauth2ClientRepository: repository.NewOauth2Client,
|
||||
}
|
||||
|
||||
// Oauth2ClientService 用户授权第三方应用信息 服务层处理
|
||||
type Oauth2ClientService struct {
|
||||
oauth2ClientRepository *repository.Oauth2Client // 用户授权第三方应用表
|
||||
}
|
||||
|
||||
// FindByPage 分页查询
|
||||
func (s Oauth2ClientService) FindByPage(query map[string]string) ([]model.Oauth2Client, int64) {
|
||||
return s.oauth2ClientRepository.SelectByPage(query)
|
||||
}
|
||||
|
||||
// FindById 查询ID
|
||||
func (s Oauth2ClientService) FindById(id int64) model.Oauth2Client {
|
||||
rows := s.oauth2ClientRepository.SelectByIds([]int64{id})
|
||||
if len(rows) > 0 {
|
||||
return rows[0]
|
||||
}
|
||||
return model.Oauth2Client{}
|
||||
}
|
||||
|
||||
// FindByClientId 查询ClientId
|
||||
func (s Oauth2ClientService) FindByClientId(clientId string) model.Oauth2Client {
|
||||
return s.oauth2ClientRepository.SelectByClientId(clientId)
|
||||
}
|
||||
|
||||
// Insert 新增
|
||||
func (s Oauth2ClientService) Insert(param model.Oauth2Client) int64 {
|
||||
param.ClientId = generate.Code(16)
|
||||
param.ClientSecret = generate.Code(32)
|
||||
return s.oauth2ClientRepository.Insert(param)
|
||||
}
|
||||
|
||||
// Update 更新
|
||||
func (s Oauth2ClientService) Update(param model.Oauth2Client) int64 {
|
||||
return s.oauth2ClientRepository.Update(param)
|
||||
}
|
||||
|
||||
// DeleteByIds 批量删除
|
||||
func (s Oauth2ClientService) DeleteByIds(ids []int64) (int64, error) {
|
||||
// 检查是否存在
|
||||
arr := s.oauth2ClientRepository.SelectByIds(ids)
|
||||
if len(arr) <= 0 {
|
||||
// return 0, fmt.Errorf("没有权限访问用户授权第三方应用数据!")
|
||||
return 0, fmt.Errorf("no permission to access user-authorized third-party application data")
|
||||
}
|
||||
if len(arr) == len(ids) {
|
||||
return s.oauth2ClientRepository.DeleteByIds(ids), nil
|
||||
}
|
||||
// return 0, fmt.Errorf("删除用户授权第三方应用信息失败!")
|
||||
return 0, fmt.Errorf("failed to delete user-authorized third-party application information")
|
||||
}
|
||||
85
src/modules/auth/service/oauth2_log_login.go
Normal file
85
src/modules/auth/service/oauth2_log_login.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/utils/date"
|
||||
"be.ems/src/framework/utils/file"
|
||||
"be.ems/src/modules/auth/model"
|
||||
"be.ems/src/modules/auth/repository"
|
||||
)
|
||||
|
||||
// NewOauth2LogLogin 实例化服务层
|
||||
var NewOauth2LogLogin = &Oauth2LogLoginService{
|
||||
oauth2LogLoginRepository: repository.NewOauth2LogLogin,
|
||||
}
|
||||
|
||||
// Oauth2LogLogin 用户授权第三方应用登录日志 服务层处理
|
||||
type Oauth2LogLoginService struct {
|
||||
oauth2LogLoginRepository *repository.Oauth2LogLoginRepository // 用户授权第三方应用登录日志信息
|
||||
}
|
||||
|
||||
// FindByPage 分页查询列表数据
|
||||
func (s Oauth2LogLoginService) FindByPage(query map[string]string) ([]model.Oauth2LogLogin, int64) {
|
||||
return s.oauth2LogLoginRepository.SelectByPage(query)
|
||||
}
|
||||
|
||||
// Insert 新增信息
|
||||
func (s Oauth2LogLoginService) Insert(clientId, status, msg string, ilobArr [4]string) int64 {
|
||||
sysOauth2LogLogin := model.Oauth2LogLogin{
|
||||
LoginIp: ilobArr[0],
|
||||
LoginLocation: ilobArr[1],
|
||||
OS: ilobArr[2],
|
||||
Browser: ilobArr[3],
|
||||
ClientId: clientId,
|
||||
StatusFlag: status,
|
||||
Msg: msg,
|
||||
}
|
||||
return s.oauth2LogLoginRepository.Insert(sysOauth2LogLogin)
|
||||
}
|
||||
|
||||
// Clean 清空用户授权第三方应用登录日志
|
||||
func (s Oauth2LogLoginService) Clean() int64 {
|
||||
return s.oauth2LogLoginRepository.Clean()
|
||||
}
|
||||
|
||||
// ExportData 导出数据表格
|
||||
func (s Oauth2LogLoginService) ExportData(rows []model.Oauth2LogLogin, fileName string) (string, error) {
|
||||
// 第一行表头标题
|
||||
headerCells := map[string]string{
|
||||
"A1": "序号",
|
||||
"B1": "应用的唯一标识",
|
||||
"C1": "登录状态",
|
||||
"D1": "登录地址",
|
||||
"E1": "登录地点",
|
||||
"F1": "浏览器",
|
||||
"G1": "操作系统",
|
||||
"H1": "提示消息",
|
||||
"I1": "访问时间",
|
||||
}
|
||||
// 从第二行开始的数据
|
||||
dataCells := make([]map[string]any, 0)
|
||||
for i, row := range rows {
|
||||
idx := strconv.Itoa(i + 2)
|
||||
// 状态
|
||||
statusValue := "失败"
|
||||
if row.StatusFlag == constants.STATUS_YES {
|
||||
statusValue = "成功"
|
||||
}
|
||||
dataCells = append(dataCells, map[string]any{
|
||||
"A" + idx: row.ID,
|
||||
"B" + idx: row.ClientId,
|
||||
"C" + idx: statusValue,
|
||||
"D" + idx: row.LoginIp,
|
||||
"E" + idx: row.LoginLocation,
|
||||
"F" + idx: row.Browser,
|
||||
"G" + idx: row.OS,
|
||||
"H" + idx: row.Msg,
|
||||
"I" + idx: date.ParseDateToStr(row.LoginTime, date.YYYY_MM_DD_HH_MM_SS),
|
||||
})
|
||||
}
|
||||
|
||||
// 导出数据表格
|
||||
return file.WriteSheet(headerCells, dataCells, fileName, "")
|
||||
}
|
||||
Reference in New Issue
Block a user