✨ add api validation signature
This commit is contained in:
@@ -15,3 +15,6 @@ type AddPermissionToRoleRequestDto struct {
|
||||
Permission string `json:"permission"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
type GetPermissionRequest struct {
|
||||
UserId string `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
@@ -71,12 +71,13 @@ func (PermissionAPI) AssignPermissionsToRole(c *gin.Context) {
|
||||
|
||||
// GetUserPermissions 获取用户角色权限
|
||||
func (PermissionAPI) GetUserPermissions(c *gin.Context) {
|
||||
userId := c.Query("user_id")
|
||||
if userId == "" {
|
||||
result.FailWithMessage("user_id is required", c)
|
||||
getPermissionRequest := dto.GetPermissionRequest{}
|
||||
err := c.ShouldBindJSON(&getPermissionRequest)
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
return
|
||||
}
|
||||
data, err := global.Casbin.GetImplicitRolesForUser(userId)
|
||||
data, err := global.Casbin.GetImplicitRolesForUser(getPermissionRequest.UserId)
|
||||
if err != nil {
|
||||
result.FailWithMessage("Get user permissions failed", c)
|
||||
return
|
||||
|
7
api/sms_api/dto/request_dto.go
Normal file
7
api/sms_api/dto/request_dto.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package dto
|
||||
|
||||
type SmsRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Angle int64 `json:"angle" binding:"required"`
|
||||
Key string `json:"key" binding:"required"`
|
||||
}
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/pkg6/go-sms/gateways"
|
||||
"github.com/pkg6/go-sms/gateways/aliyun"
|
||||
"github.com/pkg6/go-sms/gateways/smsbao"
|
||||
"schisandra-cloud-album/api/sms_api/dto"
|
||||
"schisandra-cloud-album/common/constant"
|
||||
"schisandra-cloud-album/common/redis"
|
||||
"schisandra-cloud-album/common/result"
|
||||
@@ -23,17 +24,23 @@ import (
|
||||
// @Param phone query string true "手机号"
|
||||
// @Router /api/sms/ali/send [get]
|
||||
func (SmsAPI) SendMessageByAli(c *gin.Context) {
|
||||
phone := c.Query("phone")
|
||||
if phone == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c)
|
||||
smsRequest := dto.SmsRequest{}
|
||||
err := c.ShouldBindJSON(&smsRequest)
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(phone)
|
||||
checkRotateData := utils.CheckRotateData(smsRequest.Angle, smsRequest.Key)
|
||||
if !checkRotateData {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaVerifyError"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(smsRequest.Phone)
|
||||
if !isPhone {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c)
|
||||
return
|
||||
}
|
||||
val := redis.Get(constant.UserLoginSmsRedisKey + phone).Val()
|
||||
val := redis.Get(constant.UserLoginSmsRedisKey + smsRequest.Phone).Val()
|
||||
if val != "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaTooOften"), c)
|
||||
return
|
||||
@@ -46,12 +53,12 @@ func (SmsAPI) SendMessageByAli(c *gin.Context) {
|
||||
},
|
||||
})
|
||||
code := utils.GenValidateCode(6)
|
||||
wrong := redis.Set(constant.UserLoginSmsRedisKey+phone, code, time.Minute).Err()
|
||||
wrong := redis.Set(constant.UserLoginSmsRedisKey+smsRequest.Phone, code, time.Minute).Err()
|
||||
if wrong != nil {
|
||||
global.LOG.Error(wrong)
|
||||
return
|
||||
}
|
||||
_, err := sms.Send(phone, gosms.MapStringAny{
|
||||
_, err = sms.Send(smsRequest.Phone, gosms.MapStringAny{
|
||||
"content": "您的验证码是:****。请不要把验证码泄露给其他人。",
|
||||
"template": global.CONFIG.SMS.Ali.TemplateID,
|
||||
//"signName": global.CONFIG.SMS.Ali.Signature,
|
||||
@@ -74,19 +81,25 @@ func (SmsAPI) SendMessageByAli(c *gin.Context) {
|
||||
// @Tags 短信验证码
|
||||
// @Produce json
|
||||
// @Param phone query string true "手机号"
|
||||
// @Router /api/sms/smsbao/send [get]
|
||||
// @Router /api/sms/smsbao/send [post]
|
||||
func (SmsAPI) SendMessageBySmsBao(c *gin.Context) {
|
||||
phone := c.Query("phone")
|
||||
if phone == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c)
|
||||
smsRequest := dto.SmsRequest{}
|
||||
err := c.ShouldBindJSON(&smsRequest)
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(phone)
|
||||
checkRotateData := utils.CheckRotateData(smsRequest.Angle, smsRequest.Key)
|
||||
if !checkRotateData {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaVerifyError"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(smsRequest.Phone)
|
||||
if !isPhone {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c)
|
||||
return
|
||||
}
|
||||
val := redis.Get(constant.UserLoginSmsRedisKey + phone).Val()
|
||||
val := redis.Get(constant.UserLoginSmsRedisKey + smsRequest.Phone).Val()
|
||||
if val != "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaTooOften"), c)
|
||||
return
|
||||
@@ -98,12 +111,12 @@ func (SmsAPI) SendMessageBySmsBao(c *gin.Context) {
|
||||
},
|
||||
})
|
||||
code := utils.GenValidateCode(6)
|
||||
wrong := redis.Set(constant.UserLoginSmsRedisKey+phone, code, time.Minute).Err()
|
||||
wrong := redis.Set(constant.UserLoginSmsRedisKey+smsRequest.Phone, code, time.Minute).Err()
|
||||
if wrong != nil {
|
||||
global.LOG.Error(wrong)
|
||||
return
|
||||
}
|
||||
_, err := sms.Send(phone, gosms.MapStringAny{
|
||||
_, err = sms.Send(smsRequest.Phone, gosms.MapStringAny{
|
||||
"content": "您的验证码是:" + code + "。请不要把验证码泄露给其他人。",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
@@ -120,20 +133,26 @@ func (SmsAPI) SendMessageBySmsBao(c *gin.Context) {
|
||||
// @Tags 短信验证码
|
||||
// @Produce json
|
||||
// @Param phone query string true "手机号"
|
||||
// @Router /api/sms/test/send [get]
|
||||
// @Router /api/sms/test/send [post]
|
||||
func (SmsAPI) SendMessageTest(c *gin.Context) {
|
||||
phone := c.Query("phone")
|
||||
if phone == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c)
|
||||
smsRequest := dto.SmsRequest{}
|
||||
err := c.ShouldBindJSON(&smsRequest)
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(phone)
|
||||
checkRotateData := utils.CheckRotateData(smsRequest.Angle, smsRequest.Key)
|
||||
if !checkRotateData {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaVerifyError"), c)
|
||||
return
|
||||
}
|
||||
isPhone := utils.IsPhone(smsRequest.Phone)
|
||||
if !isPhone {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneError"), c)
|
||||
return
|
||||
}
|
||||
code := utils.GenValidateCode(6)
|
||||
err := redis.Set(constant.UserLoginSmsRedisKey+phone, code, time.Minute).Err()
|
||||
err = redis.Set(constant.UserLoginSmsRedisKey+smsRequest.Phone, code, time.Minute).Err()
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
|
@@ -19,6 +19,8 @@ type AccountLoginRequest struct {
|
||||
Account string `json:"account" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
AutoLogin bool `json:"auto_login" binding:"required"`
|
||||
Angle int64 `json:"angle" binding:"required"`
|
||||
Key string `json:"key" binding:"required"`
|
||||
}
|
||||
|
||||
// AddUserRequest 新增用户请求
|
||||
|
@@ -117,6 +117,11 @@ func (UserAPI) AccountLogin(c *gin.Context) {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
|
||||
return
|
||||
}
|
||||
rotateData := utils.CheckRotateData(accountLoginRequest.Angle, accountLoginRequest.Key)
|
||||
if !rotateData {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaVerifyError"), c)
|
||||
return
|
||||
}
|
||||
account := accountLoginRequest.Account
|
||||
password := accountLoginRequest.Password
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package constant
|
||||
|
||||
// 登录相关的redis key
|
||||
const (
|
||||
// 登录相关的redis key
|
||||
UserLoginSmsRedisKey = "user:sms:"
|
||||
UserLoginTokenRedisKey = "user:token:"
|
||||
UserLoginCaptchaRedisKey = "user:captcha:"
|
||||
@@ -10,8 +10,14 @@ const (
|
||||
UserSessionRedisKey = "user:session:"
|
||||
)
|
||||
|
||||
// 登录之后
|
||||
// 评论相关的redis key
|
||||
const (
|
||||
CommentSubmitCaptchaRedisKey = "comment:submit:captcha:"
|
||||
CommentOfflineMessageRedisKey = "comment:offline:message:"
|
||||
)
|
||||
|
||||
// 系统相关的redis key
|
||||
|
||||
const (
|
||||
SystemApiNonceRedisKey = "system:api:nonce:"
|
||||
)
|
||||
|
@@ -73,4 +73,5 @@ CommentLikeSuccess = "comment like success!"
|
||||
CommentLikeFailed = "comment like failed!"
|
||||
CommentDislikeSuccess = "comment dislike success!"
|
||||
CommentDislikeFailed = "comment dislike failed!"
|
||||
CaptchaVerifyError = "captcha error!"
|
||||
CaptchaVerifyError = "captcha error!"
|
||||
RequestVerifyError = "request verify error!"
|
@@ -74,3 +74,4 @@ CommentLikeFailed = "评论点赞失败!"
|
||||
CommentDislikeSuccess = "评论取消点赞成功!"
|
||||
CommentDislikeFailed = "评论取消点赞失败!"
|
||||
CaptchaVerifyError = "验证失败!"
|
||||
RequestVerifyError = "请求验证失败!"
|
||||
|
93
middleware/verify_signature.go
Normal file
93
middleware/verify_signature.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
"schisandra-cloud-album/common/constant"
|
||||
"schisandra-cloud-album/common/redis"
|
||||
"schisandra-cloud-album/common/result"
|
||||
"schisandra-cloud-album/global"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func VerifySignature() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 仅处理 POST 请求
|
||||
if c.Request.Method != http.MethodPost {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
// 从请求头获取签名和时间戳
|
||||
signature := c.GetHeader("X-Sign")
|
||||
timestamp := c.GetHeader("X-Timestamp")
|
||||
nonce := c.GetHeader("X-Nonce")
|
||||
secretKey := global.CONFIG.Encrypt.Key
|
||||
|
||||
// 检查时间戳是否过期,这里设置为5分钟过期
|
||||
if timestamp == "" || time.Since(parseTimestamp(timestamp)) > 5*time.Minute {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "RequestVerifyError"), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查 nonce 是否已经被使用
|
||||
if data := redis.Get(constant.SystemApiNonceRedisKey + nonce).Val(); data != "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "RequestVerifyError"), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 记录 nonce 到 Redis 中,并设置过期时间为 5 分钟
|
||||
if err := redis.Set(constant.SystemApiNonceRedisKey+nonce, true, 5*time.Minute).Err(); err != nil {
|
||||
global.LOG.Error(err.Error())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取请求方法和请求体
|
||||
var payload string
|
||||
if c.Request.Method == http.MethodPost {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "RequestReadError"), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
payload = string(body)
|
||||
// 重新设置请求体,以便后续处理中可以再次读取
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
// 创建待签名字符串
|
||||
baseString := c.Request.Method + ":" + payload + ":" + timestamp + ":" + nonce + ":" + secretKey
|
||||
|
||||
// 生成 MD5 签名
|
||||
h := md5.New()
|
||||
h.Write([]byte(baseString))
|
||||
expectedSignature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
// 验证签名
|
||||
if signature != expectedSignature {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "RequestVerifyError"), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 继续处理请求
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:解析时间戳
|
||||
func parseTimestamp(ts string) time.Time {
|
||||
t, err := strconv.ParseInt(ts, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{} // 解析错误返回零时间
|
||||
}
|
||||
return time.Unix(t/1000, 0) // 假设时间戳是毫秒
|
||||
}
|
@@ -11,6 +11,6 @@ func PermissionRouter(router *gin.RouterGroup) {
|
||||
group := router.Group("/auth/permission")
|
||||
{
|
||||
group.POST("/add", permissionApi.AddPermissions)
|
||||
group.GET("/get_user_permissions", permissionApi.GetUserPermissions)
|
||||
group.POST("/get_user_permissions", permissionApi.GetUserPermissions)
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ var smsApi = api.Api.SmsApi
|
||||
|
||||
func SmsRouter(router *gin.RouterGroup) {
|
||||
group := router.Group("/sms")
|
||||
group.GET("/ali/send", smsApi.SendMessageByAli)
|
||||
group.GET("/smsbao/send", smsApi.SendMessageBySmsBao)
|
||||
group.GET("/test/send", smsApi.SendMessageTest)
|
||||
group.POST("/ali/send", smsApi.SendMessageByAli)
|
||||
group.POST("/smsbao/send", smsApi.SendMessageBySmsBao)
|
||||
group.POST("/test/send", smsApi.SendMessageTest)
|
||||
}
|
||||
|
@@ -50,6 +50,7 @@ func InitRouter() *gin.Engine {
|
||||
middleware.SecurityHeaders(),
|
||||
middleware.JWTAuthMiddleware(),
|
||||
middleware.CasbinMiddleware(),
|
||||
middleware.VerifySignature(),
|
||||
)
|
||||
{
|
||||
modules.UserRouterAuth(authGroup) // 注册鉴权路由
|
||||
|
@@ -38,8 +38,8 @@ func CheckSlideData(point []int64, key string) bool {
|
||||
}
|
||||
|
||||
// CheckRotateData 校验旋转验证码
|
||||
func CheckRotateData(angle string, key string) bool {
|
||||
if angle == "" || key == "" {
|
||||
func CheckRotateData(angle int64, key string) bool {
|
||||
if angle == 0 || key == "" {
|
||||
return false
|
||||
}
|
||||
cacheDataByte, err := redis.Get(constant.UserLoginCaptchaRedisKey + key).Bytes()
|
||||
|
Reference in New Issue
Block a user