feat: 支持redis缓存权限等信息

This commit is contained in:
meilin.huang
2022-12-26 20:53:07 +08:00
parent 4fec38724d
commit 4a26bb3ba5
11 changed files with 200 additions and 86 deletions

View File

@@ -24,9 +24,15 @@ mysql:
db-name: mayfly-go db-name: mayfly-go
config: charset=utf8&loc=Local&parseTime=true config: charset=utf8&loc=Local&parseTime=true
max-idle-conns: 5 max-idle-conns: 5
# 若同时部署多台机器则需要配置redis信息用于缓存权限码、验证码、公私钥等
# redis:
# host: localhost
# port: 6379
# passsord:
# db: 0
log: log:
# 日志等级, trace, debug, info, warn, error, fatal # 日志等级, trace, debug, info, warn, error, fatal
level: info level: info
file: # file:
path: ./ # path: ./
name: mayfly-go.log # name: mayfly-go.log

View File

@@ -3,10 +3,10 @@ module mayfly-go
go 1.19 go 1.19
require ( require (
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.2
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v4 v4.4.2 github.com/golang-jwt/jwt/v4 v4.4.3
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.6 github.com/lib/pq v1.10.6
github.com/mojocn/base64Captcha v1.3.5 // github.com/mojocn/base64Captcha v1.3.5 //
@@ -28,8 +28,8 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.7 // indirect github.com/goccy/go-json v0.9.11 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
@@ -38,11 +38,11 @@ require (
github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/fs v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@@ -50,11 +50,11 @@ require (
github.com/xdg-go/stringprep v1.0.3 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.3.0 // indirect golang.org/x/net v0.4.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect golang.org/x/text v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

34
server/pkg/cache/str_cache.go vendored Normal file
View File

@@ -0,0 +1,34 @@
package cache
import "mayfly-go/pkg/rediscli"
var strCache map[string]string
// 如果系统有设置redis信息则从redis获取否则本机内存获取
func GetStr(key string) string {
if rediscli.GetCli() == nil {
checkStrCache()
return strCache[key]
}
res, err := rediscli.Get(key)
if err != nil {
return ""
}
return res
}
// 如果系统有设置redis信息则使用redis存否则存于本机内存
func SetStr(key, value string) {
if rediscli.GetCli() == nil {
checkStrCache()
strCache[key] = value
return
}
rediscli.Set(key, value, 0)
}
func checkStrCache() {
if strCache == nil {
strCache = make(map[string]string)
}
}

View File

@@ -2,15 +2,25 @@ package captcha
import ( import (
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/rediscli"
"time"
"github.com/mojocn/base64Captcha" "github.com/mojocn/base64Captcha"
) )
var store = base64Captcha.DefaultMemStore var store base64Captcha.Store
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
// 生成验证码 // 生成验证码
func Generate() (string, string) { func Generate() (string, string) {
if store == nil {
if rediscli.GetCli() != nil {
store = new(RedisStore)
} else {
store = base64Captcha.DefaultMemStore
}
}
c := base64Captcha.NewCaptcha(driver, store) c := base64Captcha.NewCaptcha(driver, store)
// 获取 // 获取
id, b64s, err := c.Generate() id, b64s, err := c.Generate()
@@ -26,3 +36,34 @@ func Verify(id string, val string) bool {
// 同时清理掉这个图片 // 同时清理掉这个图片
return store.Verify(id, val, true) return store.Verify(id, val, true)
} }
type RedisStore struct {
}
const CAPTCHA = "mayfly:captcha:"
// 实现设置captcha的方法
func (r RedisStore) Set(id string, value string) error {
//time.Minute*2有效时间2分钟
rediscli.Set(CAPTCHA+id, value, time.Minute*2)
return nil
}
// 实现获取captcha的方法
func (r RedisStore) Get(id string, clear bool) string {
key := CAPTCHA + id
val, err := rediscli.Get(key)
if err != nil {
return ""
}
if clear {
//clear为true验证通过删除这个验证码
rediscli.Del(key)
}
return val
}
// 实现验证captcha的方法
func (r RedisStore) Verify(id, answer string, clear bool) bool {
return r.Get(id, clear) == answer
}

View File

@@ -40,8 +40,8 @@ type Config struct {
Server *Server `yaml:"server"` Server *Server `yaml:"server"`
Jwt *Jwt `yaml:"jwt"` Jwt *Jwt `yaml:"jwt"`
Aes *Aes `yaml:"aes"` Aes *Aes `yaml:"aes"`
Redis *Redis `yaml:"redis"`
Mysql *Mysql `yaml:"mysql"` Mysql *Mysql `yaml:"mysql"`
Redis *Redis `yaml:"redis"`
Log *Log `yaml:"log"` Log *Log `yaml:"log"`
} }

View File

@@ -1,10 +1,13 @@
package ctx package ctx
import ( import (
"encoding/json"
"fmt" "fmt"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/cache" "mayfly-go/pkg/cache"
"mayfly-go/pkg/config" "mayfly-go/pkg/config"
"mayfly-go/pkg/rediscli"
"mayfly-go/pkg/utils"
"time" "time"
) )
@@ -22,6 +25,62 @@ func (p *Permission) WithNeedToken(needToken bool) *Permission {
return p return p
} }
var (
permissionCodeRegistry PermissionCodeRegistry
)
func PermissionHandler(rc *ReqCtx) error {
if permissionCodeRegistry == nil {
if rediscli.GetCli() == nil {
permissionCodeRegistry = new(DefaultPermissionCodeRegistry)
} else {
permissionCodeRegistry = new(RedisPermissionCodeRegistry)
}
}
permission := rc.RequiredPermission
// 如果需要的权限信息不为空并且不需要token则不返回错误继续后续逻辑
if permission != nil && !permission.NeedToken {
return nil
}
tokenStr := rc.GinCtx.Request.Header.Get("Authorization")
// header不存在则从查询参数token中获取
if tokenStr == "" {
tokenStr = rc.GinCtx.Query("token")
}
if tokenStr == "" {
return biz.PermissionErr
}
loginAccount, err := ParseToken(tokenStr)
if err != nil || loginAccount == nil {
return biz.PermissionErr
}
// 权限不为nil并且permission code不为空则校验是否有权限code
if permission != nil && permission.Code != "" {
if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
return biz.PermissionErr
}
}
rc.LoginAccount = loginAccount
return nil
}
// 保存用户权限code
func SavePermissionCodes(userId uint64, codes []string) {
permissionCodeRegistry.SaveCodes(userId, codes)
}
// 删除用户权限code
func DeletePermissionCodes(userId uint64) {
permissionCodeRegistry.Remove(userId)
}
// 设置权限code注册器
func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) {
permissionCodeRegistry = pcr
}
type PermissionCodeRegistry interface { type PermissionCodeRegistry interface {
// 保存用户权限code // 保存用户权限code
SaveCodes(userId uint64, codes []string) SaveCodes(userId uint64, codes []string)
@@ -63,51 +122,29 @@ func (r *DefaultPermissionCodeRegistry) Remove(userId uint64) {
r.cache.Delete(fmt.Sprintf("%v", userId)) r.cache.Delete(fmt.Sprintf("%v", userId))
} }
// 保存用户权限code type RedisPermissionCodeRegistry struct {
func SavePermissionCodes(userId uint64, codes []string) {
permissionCodeRegistry.SaveCodes(userId, codes)
} }
// 删除用户权限code func (r *RedisPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) {
func DeletePermissionCodes(userId uint64) { rediscli.Set(fmt.Sprintf("mayfly:%v:codes", userId), utils.ToString(codes), time.Minute*time.Duration(config.Conf.Jwt.ExpireTime))
permissionCodeRegistry.Remove(userId)
} }
// 设置权限code注册器 func (r *RedisPermissionCodeRegistry) HasCode(userId uint64, code string) bool {
func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) { str, err := rediscli.Get(fmt.Sprintf("mayfly:%v:codes", userId))
permissionCodeRegistry = pcr if err != nil || str == "" {
} return false
}
var ( var codes []string
permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{} _ = json.Unmarshal([]byte(str), &codes)
// permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg) for _, v := range codes {
) if v == code {
return true
func PermissionHandler(rc *ReqCtx) error {
permission := rc.RequiredPermission
// 如果需要的权限信息不为空并且不需要token则不返回错误继续后续逻辑
if permission != nil && !permission.NeedToken {
return nil
}
tokenStr := rc.GinCtx.Request.Header.Get("Authorization")
// header不存在则从查询参数token中获取
if tokenStr == "" {
tokenStr = rc.GinCtx.Query("token")
}
if tokenStr == "" {
return biz.PermissionErr
}
loginAccount, err := ParseToken(tokenStr)
if err != nil || loginAccount == nil {
return biz.PermissionErr
}
// 权限不为nil并且permission code不为空则校验是否有权限code
if permission != nil && permission.Code != "" {
if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
return biz.PermissionErr
} }
} }
return false
rc.LoginAccount = loginAccount }
return nil
func (r *RedisPermissionCodeRegistry) Remove(userId uint64) {
rediscli.Del(fmt.Sprintf("mayfly:%v:codes", userId))
} }

View File

@@ -1,13 +1,11 @@
package global package global
import ( import (
"github.com/go-redis/redis/v8"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
var ( var (
Log *logrus.Logger // 日志 Log *logrus.Logger // 日志
Db *gorm.DB // gorm Db *gorm.DB // gorm
RedisCli *redis.Client // redis
) )

View File

@@ -2,7 +2,6 @@ package rediscli
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
@@ -19,17 +18,8 @@ func GetCli() *redis.Client {
} }
// get key value // get key value
func Get(key string) string { func Get(key string) (string, error) {
val, err := cli.Get(context.TODO(), key).Result() return cli.Get(context.TODO(), key).Result()
switch {
case err == redis.Nil:
fmt.Println("key does not exist")
case err != nil:
fmt.Println("Get failed", err)
case val == "":
fmt.Println("value is empty")
}
return val
} }
// set key value // set key value
@@ -37,6 +27,10 @@ func Set(key string, val string, expiration time.Duration) {
cli.Set(context.TODO(), key, val, expiration) cli.Set(context.TODO(), key, val, expiration)
} }
func Del(key string) {
cli.Del(context.TODO(), key)
}
func HSet(key string, field string, val interface{}) { func HSet(key string, field string, val interface{}) {
cli.HSet(context.TODO(), key, field, val) cli.HSet(context.TODO(), key, field, val)
} }

View File

@@ -5,19 +5,20 @@ import (
"fmt" "fmt"
"mayfly-go/pkg/config" "mayfly-go/pkg/config"
"mayfly-go/pkg/global" "mayfly-go/pkg/global"
"mayfly-go/pkg/rediscli"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
) )
func InitRedis() { func initRedis() {
global.RedisCli = ConnRedis() rediscli.SetCli(connRedis())
} }
func ConnRedis() *redis.Client { func connRedis() *redis.Client {
// 设置redis客户端 // 设置redis客户端
redisConf := config.Conf.Redis redisConf := config.Conf.Redis
if redisConf == nil { if redisConf == nil {
global.Log.Panic("未找到redis配置信息") // global.Log.Panic("未找到redis配置信息")
return nil return nil
} }
global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port) global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)

View File

@@ -18,6 +18,8 @@ func RunWebServer() {
printBanner() printBanner()
// 初始化并赋值数据库全局变量 // 初始化并赋值数据库全局变量
initDb() initDb()
// 有配置redis信息则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等
initRedis()
// 运行web服务 // 运行web服务
runWebServer() runWebServer()
} }

View File

@@ -12,6 +12,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/pem" "encoding/pem"
"errors" "errors"
"mayfly-go/pkg/cache"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -34,9 +35,6 @@ func CheckPwdHash(password, hash string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
} }
// 系统统一RSA秘钥对
var RsaPair []string
// 生成RSA私钥和公钥字符串 // 生成RSA私钥和公钥字符串
// bits 证书大小 // bits 证书大小
// @return privateKeyStr publicKeyStr error // @return privateKeyStr publicKeyStr error
@@ -130,33 +128,36 @@ func DefaultRsaDecrypt(data string, useBase64 bool) (string, error) {
return string(val), nil return string(val), nil
} }
const publicKeyK = "mayfly:public-key"
const privateKeyK = "mayfly:private-key"
// 获取系统的RSA公钥 // 获取系统的RSA公钥
func GetRsaPublicKey() (string, error) { func GetRsaPublicKey() (string, error) {
if len(RsaPair) == 2 { publicKey := cache.GetStr(publicKeyK)
return RsaPair[1], nil if publicKey != "" {
return publicKey, nil
} }
privateKey, publicKey, err := GenerateRSAKey(1024) privateKey, publicKey, err := GenerateRSAKey(1024)
if err != nil { if err != nil {
return "", err return "", err
} }
RsaPair = append(RsaPair, privateKey) cache.SetStr(publicKeyK, publicKey)
RsaPair = append(RsaPair, publicKey) cache.SetStr(privateKeyK, privateKey)
return publicKey, nil return publicKey, nil
} }
// 获取系统私钥 // 获取系统私钥
func GetRsaPrivateKey() (string, error) { func GetRsaPrivateKey() (string, error) {
if len(RsaPair) == 2 { privateKey := cache.GetStr(privateKeyK)
return RsaPair[0], nil if privateKey != "" {
return privateKey, nil
} }
privateKey, publicKey, err := GenerateRSAKey(1024) privateKey, publicKey, err := GenerateRSAKey(1024)
if err != nil { if err != nil {
return "", err return "", err
} }
RsaPair = append(RsaPair, privateKey) cache.SetStr(publicKeyK, publicKey)
RsaPair = append(RsaPair, publicKey) cache.SetStr(privateKeyK, privateKey)
return privateKey, nil return privateKey, nil
} }