feat: 客户端oauth2认证模块

This commit is contained in:
TsMask
2025-08-12 10:35:18 +08:00
parent 9e57c145d1
commit 72e04a140d
15 changed files with 252 additions and 102 deletions

View File

@@ -17,7 +17,6 @@ import (
networkelement "be.ems/src/modules/network_element"
"be.ems/src/modules/notification"
"be.ems/src/modules/oam"
"be.ems/src/modules/oauth2"
"be.ems/src/modules/system"
"be.ems/src/modules/tool"
"be.ems/src/modules/trace"
@@ -84,10 +83,9 @@ func ModulesRoute(app *gin.Engine) {
system.Setup(app)
// 认证模块
auth.Setup(app)
// 开放客户端模块
oauth2.Setup(app)
// 网元OAM对接
oam.Setup(app)
oam.SetupOauth2(app)
// 通用模块
common.Setup(app)

View File

@@ -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,
)
}
}

View File

@@ -11,8 +11,8 @@ import (
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/token"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/oauth2/service"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/service"
)
// NewOauth2 实例化控制层

View 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))
}

View File

@@ -8,3 +8,10 @@ type TokenBody struct {
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"` // 随机字符串,认证服务器会原封不动地返回这个值
}

View File

@@ -6,7 +6,7 @@ import (
"be.ems/src/framework/database/db"
"be.ems/src/framework/logger"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/auth/model"
)
// NewOauth2Client 实例化数据层

View File

@@ -7,7 +7,7 @@ import (
"be.ems/src/framework/database/db"
"be.ems/src/framework/logger"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/auth/model"
)
// NewOauth2LogLogin 实例化数据层
@@ -69,9 +69,10 @@ func (r Oauth2LogLoginRepository) SelectByPage(query map[string]string) ([]model
sortOrder := sortOrderArr[i]
// 排序字段
sort := "id"
if sortBy == "loginIp" {
switch sortBy {
case "loginIp":
sort = "login_ip"
} else if sortBy == "createTime" {
case "createTime":
sort = "create_time"
}
// 排序方式

View File

@@ -10,8 +10,8 @@ import (
"be.ems/src/framework/token"
"be.ems/src/framework/utils/crypto"
"be.ems/src/framework/utils/generate"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/oauth2/repository"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/repository"
)
// NewOauth2Service 实例化服务层

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"be.ems/src/framework/utils/generate"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/oauth2/repository"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/repository"
)
// NewOauth2ClientService 实例化服务层
@@ -55,11 +55,11 @@ 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!")
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!")
return 0, fmt.Errorf("failed to delete user-authorized third-party application information")
}

View File

@@ -6,8 +6,8 @@ import (
"be.ems/src/framework/constants"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/file"
"be.ems/src/modules/oauth2/model"
"be.ems/src/modules/oauth2/repository"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/repository"
)
// NewOauth2LogLogin 实例化服务层

View File

@@ -1,4 +1,4 @@
package oauth2
package oam
import (
"github.com/gin-gonic/gin"
@@ -8,8 +8,8 @@ import (
neController "be.ems/src/modules/network_element/controller"
)
// openAPI 客户端授权开放接口
func openAPI(router *gin.Engine) {
// SetupOauth2 客户端授权开放接口
func SetupOauth2(router *gin.Engine) {
openApiGroup := router.Group("/open-api")
// 监控

View File

@@ -1,8 +0,0 @@
package model
// CodeQuery 重定向授权码参数
type CodeQuery struct {
RedirectUrl string `form:"redirectUrl" binding:"required"` // 授权回调地址
ClientId string `form:"clientId" binding:"required"` // 申请得到的客户端ID
State string `form:"state" binding:"required"` // 随机字符串,认证服务器会原封不动地返回这个值
}

View File

@@ -1,74 +0,0 @@
package oauth2
import (
"github.com/gin-gonic/gin"
"be.ems/src/framework/logger"
"be.ems/src/framework/middleware"
"be.ems/src/modules/oauth2/controller"
)
// Setup 模块路由注册
func Setup(router *gin.Engine) {
logger.Infof("开始加载 ====> oauth2 模块路由")
// 客户端授权管理
oauth2ClientGroup := router.Group("/oauth2/client")
{
oauth2ClientGroup.GET("/list",
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
controller.NewOauth2Client.List,
)
oauth2ClientGroup.GET("/:clientId",
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
controller.NewOauth2Client.Info,
)
oauth2ClientGroup.POST("",
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_INSERT)),
controller.NewOauth2Client.Add,
)
oauth2ClientGroup.PUT("",
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_UPDATE)),
controller.NewOauth2Client.Edit,
)
oauth2ClientGroup.DELETE("/:id",
middleware.AuthorizeUser(map[string][]string{"matchRoles": {"admin"}}),
middleware.OperateLog(middleware.OptionNew("log.operate.title.oauth2client", middleware.BUSINESS_TYPE_DELETE)),
controller.NewOauth2Client.Remove,
)
}
// 授权认证
oauth2Group := router.Group("/oauth2")
{
oauth2Group.GET("/authorize",
middleware.RateLimit(middleware.LimitOption{
Time: 60,
Count: 30,
Type: middleware.LIMIT_IP,
}),
controller.NewOauth2.Authorize,
)
oauth2Group.POST("/token",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewOauth2.Token,
)
oauth2Group.POST("/refresh-token",
middleware.RateLimit(middleware.LimitOption{
Time: 60,
Count: 5,
Type: middleware.LIMIT_IP,
}),
controller.NewOauth2.RefreshToken,
)
}
// ==== 授权认证的开放接口 ====
openAPI(router)
}