feat: 登录强制校验弱密码&关键信息加密传输

This commit is contained in:
meilin.huang
2022-07-18 20:36:31 +08:00
parent db554ebdc9
commit 5271bd21e8
29 changed files with 1804 additions and 1327 deletions

View File

@@ -9,12 +9,12 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.6
github.com/mojocn/base64Captcha v1.3.5 //
github.com/pkg/sftp v1.13.4
github.com/pkg/sftp v1.13.5
github.com/robfig/cron/v3 v3.0.1 //
github.com/sirupsen/logrus v1.8.1
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.9.1 // mongo
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // ssh
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // ssh
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
// gorm
gorm.io/driver/mysql v1.3.4

View File

@@ -2,7 +2,7 @@ package initialize
import (
"fmt"
common_index_router "mayfly-go/internal/common/router"
common_router "mayfly-go/internal/common/router"
devops_router "mayfly-go/internal/devops/router"
sys_router "mayfly-go/internal/sys/router"
"mayfly-go/pkg/config"
@@ -46,7 +46,8 @@ func InitRouter() *gin.Engine {
// 设置路由组
api := router.Group("/api")
{
common_index_router.InitIndexRouter(api)
common_router.InitIndexRouter(api)
common_router.InitCommonRouter(api)
sys_router.InitCaptchaRouter(api)
sys_router.InitAccountRouter(api) // 注册account路由

View File

@@ -0,0 +1,16 @@
package api
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/utils"
)
type Common struct {
}
func (i *Common) RasPublicKey(rc *ctx.ReqCtx) {
publicKeyStr, err := utils.GetRsaPublicKey()
biz.ErrIsNilAppendErr(err, "rsa生成公私钥失败")
rc.ResData = publicKeyStr
}

View File

@@ -0,0 +1,21 @@
package router
import (
"mayfly-go/internal/common/api"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitCommonRouter(router *gin.RouterGroup) {
common := router.Group("common")
c := &api.Common{}
{
// 获取公钥
common.GET("public-key", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
WithNeedToken(false).
Handle(c.RasPublicKey)
})
}
}

View File

@@ -47,6 +47,12 @@ func (d *Db) Save(rc *ctx.ReqCtx) {
db := new(entity.Db)
utils.Copy(db, form)
// 密码解密,并使用解密后的赋值
originPwd, err := utils.DefaultRsaDecrypt(form.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
db.Password = originPwd
// 密码脱敏记录日志
form.Password = "****"
rc.ReqParam = form

View File

@@ -10,6 +10,7 @@ type Redis struct {
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
Remark string `json:"remark"`
}
type KeyInfo struct {

View File

@@ -57,6 +57,11 @@ func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
entity := new(entity.Machine)
utils.Copy(entity, machineForm)
// 密码解密,并使用解密后的赋值
originPwd, err := utils.DefaultRsaDecrypt(machineForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
entity.Password = originPwd
// 密码脱敏记录日志
machineForm.Password = "****"
rc.ReqParam = machineForm

View File

@@ -38,6 +38,11 @@ func (m *Mongo) Save(rc *ctx.ReqCtx) {
mongo := new(entity.Mongo)
utils.Copy(mongo, form)
// 解密uri并使用解密后的赋值
originUri, err := utils.DefaultRsaDecrypt(form.Uri, true)
biz.ErrIsNilAppendErr(err, "解密uri错误: %s")
mongo.Uri = originUri
mongo.SetBaseInfo(rc.LoginAccount)
m.MongoApp.Save(mongo)
}

View File

@@ -38,6 +38,12 @@ func (r *Redis) Save(rc *ctx.ReqCtx) {
redis := new(entity.Redis)
utils.Copy(redis, form)
// 密码解密,并使用解密后的赋值
originPwd, err := utils.DefaultRsaDecrypt(redis.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
redis.Password = originPwd
// 密码脱敏记录日志
form.Password = "****"
rc.ReqParam = form

View File

@@ -65,7 +65,10 @@ func (r *redisAppImpl) GetRedisBy(condition *entity.Redis, cols ...string) error
}
func (r *redisAppImpl) Save(re *entity.Redis) {
TestRedisConnection(re)
// ’修改信息且密码不为空‘ or ‘新增’需要测试是否可连接
if (re.Id != 0 && re.Password != "") || re.Id == 0 {
TestRedisConnection(re)
}
// 查找是否存在该库
oldRedis := &entity.Redis{Host: re.Host, Db: re.Db}

View File

@@ -12,6 +12,7 @@ import (
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"regexp"
"strconv"
"strings"
"time"
@@ -34,10 +35,16 @@ func (a *Account) Login(rc *ctx.ReqCtx) {
// 校验验证码
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
account := &entity.Account{Username: loginForm.Username, Password: utils.Md5(loginForm.Password)}
originPwd, err := utils.DefaultRsaDecrypt(loginForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
account := &entity.Account{Username: loginForm.Username, Password: utils.Md5(originPwd)}
biz.ErrIsNil(a.AccountApp.GetAccount(account, "Id", "Username", "Status", "LastLoginTime", "LastLoginIp"), "用户名或密码错误")
biz.IsTrue(account.IsEnable(), "该账号不可用")
// 校验密码强度是否符合
biz.IsTrueBy(CheckPasswordLever(originPwd), biz.NewBizErrCode(401, "您的密码安全等级较低,请修改后重新登录"))
var resources vo.AccountResourceVOList
// 获取账号菜单资源
a.ResourceApp.GetAccountResources(account.Id, &resources)
@@ -72,6 +79,48 @@ func (a *Account) Login(rc *ctx.ReqCtx) {
}
}
func (a *Account) ChangePassword(rc *ctx.ReqCtx) {
form := new(form.AccountChangePasswordForm)
ginx.BindJsonAndValid(rc.GinCtx, form)
originOldPwd, err := utils.DefaultRsaDecrypt(form.OldPassword, true)
biz.ErrIsNilAppendErr(err, "解密旧密码错误: %s")
account := &entity.Account{Username: form.Username, Password: utils.Md5(originOldPwd)}
biz.ErrIsNil(a.AccountApp.GetAccount(account, "Id", "Username", "Status"), "旧密码不正确")
originNewPwd, err := utils.DefaultRsaDecrypt(form.NewPassword, true)
biz.ErrIsNilAppendErr(err, "解密新密码错误: %s")
biz.IsTrue(CheckPasswordLever(originNewPwd), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
updateAccount := new(entity.Account)
updateAccount.Id = account.Id
updateAccount.Password = utils.Md5(originNewPwd)
a.AccountApp.Update(updateAccount)
// 赋值loginAccount 主要用于记录操作日志,因为操作日志保存请求上下文没有该信息不保存日志
rc.LoginAccount = &model.LoginAccount{Id: account.Id, Username: account.Username}
}
func CheckPasswordLever(ps string) bool {
if len(ps) < 8 {
return false
}
num := `[0-9]{1}`
a_z := `[a-zA-Z]{1}`
symbol := `[!@#~$%^&*()+|_.,]{1}`
if b, err := regexp.MatchString(num, ps); !b || err != nil {
return false
}
if b, err := regexp.MatchString(a_z, ps); !b || err != nil {
return false
}
if b, err := regexp.MatchString(symbol, ps); !b || err != nil {
return false
}
return true
}
// 保存更新账号登录信息
func (a *Account) saveLogin(account *entity.Account, ip string) {
// 更新账号最后登录时间
@@ -105,7 +154,7 @@ func (a *Account) saveLogin(account *entity.Account, ip string) {
}
// 获取个人账号信息
func (a Account) AccountInfo(rc *ctx.ReqCtx) {
func (a *Account) AccountInfo(rc *ctx.ReqCtx) {
ap := new(vo.AccountPersonVO)
// 角色信息
roles := new([]vo.AccountRoleVO)
@@ -116,7 +165,7 @@ func (a Account) AccountInfo(rc *ctx.ReqCtx) {
}
// 更新个人账号信息
func (a Account) UpdateAccount(rc *ctx.ReqCtx) {
func (a *Account) UpdateAccount(rc *ctx.ReqCtx) {
updateForm := &form.AccountUpdateForm{}
ginx.BindJsonAndValid(rc.GinCtx, updateForm)
@@ -126,13 +175,14 @@ func (a Account) UpdateAccount(rc *ctx.ReqCtx) {
updateAccount.Id = rc.LoginAccount.Id
if updateAccount.Password != "" {
biz.IsTrue(CheckPasswordLever(updateAccount.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
updateAccount.Password = utils.Md5(updateAccount.Password)
}
a.AccountApp.Update(updateAccount)
}
// 获取账号接收的消息列表
func (a Account) GetMsgs(rc *ctx.ReqCtx) {
func (a *Account) GetMsgs(rc *ctx.ReqCtx) {
condition := &entity.Msg{
RecipientId: int64(rc.LoginAccount.Id),
}

View File

@@ -7,3 +7,9 @@ type AccountCreateForm struct {
type AccountUpdateForm struct {
Password *string `json:"password" binding:"min=6,max=16"`
}
type AccountChangePasswordForm struct {
Username string `json:"username"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}

View File

@@ -26,6 +26,14 @@ func InitAccountRouter(router *gin.RouterGroup) {
Handle(a.Login)
})
changePwdLog := ctx.NewLogInfo("用户修改密码").WithSave(true)
account.POST("change-pwd", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
WithNeedToken(false).
WithLog(changePwdLog).
Handle(a.ChangePassword)
})
// 获取个人账号信息
account.GET("/self", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.AccountInfo)

View File

@@ -7,28 +7,28 @@ type BizError struct {
}
var (
Success *BizError = NewBizErrCode(200, "success")
BizErr *BizError = NewBizErrCode(400, "biz error")
ServerError *BizError = NewBizErrCode(500, "server error")
PermissionErr *BizError = NewBizErrCode(501, "token error")
Success BizError = NewBizErrCode(200, "success")
BizErr BizError = NewBizErrCode(400, "biz error")
ServerError BizError = NewBizErrCode(500, "server error")
PermissionErr BizError = NewBizErrCode(501, "token error")
)
// 错误消息
func (e *BizError) Error() string {
func (e BizError) Error() string {
return e.err
}
// 错误码
func (e *BizError) Code() int16 {
func (e BizError) Code() int16 {
return e.code
}
// 创建业务逻辑错误结构体,默认为业务逻辑错误
func NewBizErr(msg string) *BizError {
return &BizError{code: BizErr.code, err: msg}
func NewBizErr(msg string) BizError {
return BizError{code: BizErr.code, err: msg}
}
// 创建业务逻辑错误结构体可设置指定错误code
func NewBizErrCode(code int16, msg string) *BizError {
return &BizError{code: code, err: msg}
func NewBizErrCode(code int16, msg string) BizError {
return BizError{code: code, err: msg}
}

View File

@@ -95,7 +95,7 @@ func getErrMsg(rc *ReqCtx, err interface{}) string {
var errMsg string
switch t := err.(type) {
case *biz.BizError:
case biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
case error:
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack()))

View File

@@ -62,7 +62,7 @@ func SuccessRes(g *gin.Context, data interface{}) {
// 返回失败结果集
func ErrorRes(g *gin.Context, err interface{}) {
switch t := err.(type) {
case *biz.BizError:
case biz.BizError:
g.JSON(http.StatusOK, model.Error(t))
case error:
g.JSON(http.StatusOK, model.ServerError())

View File

@@ -44,7 +44,7 @@ func SuccessNoData() *Result {
return &Result{Code: SuccessCode, Msg: SuccessMsg}
}
func Error(bizerr *biz.BizError) *Result {
func Error(bizerr biz.BizError) *Result {
return &Result{Code: bizerr.Code(), Msg: bizerr.Error()}
}

View File

@@ -1,8 +1,15 @@
package utils
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
)
// md5
@@ -11,3 +18,115 @@ func Md5(str string) string {
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
// 系统统一RSA秘钥对
var RsaPair []string
// 生成RSA私钥和公钥字符串
// bits 证书大小
// @return privateKeyStr publicKeyStr error
func GenerateRSAKey(bits int) (string, string, error) {
var privateKeyStr, publicKeyStr string
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
//Reader是一个全局、共享的密码用强随机数生成器
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return privateKeyStr, publicKeyStr, err
}
//保存私钥
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
//构建一个pem.Block结构体对象
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
privateBuf := new(bytes.Buffer)
pem.Encode(privateBuf, &privateBlock)
privateKeyStr = privateBuf.String()
//保存公钥
//获取公钥的数据
publicKey := privateKey.PublicKey
//X509对公钥编码
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
return publicKeyStr, privateKeyStr, err
}
//创建一个pem.Block结构体对象
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
publicBuf := new(bytes.Buffer)
pem.Encode(publicBuf, &publicBlock)
publicKeyStr = publicBuf.String()
return privateKeyStr, publicKeyStr, nil
}
// rsa解密
func RsaDecrypt(privateKeyStr string, data []byte) ([]byte, error) {
block, _ := pem.Decode([]byte(privateKeyStr))
if block == nil {
return nil, errors.New("private key error")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return rsa.DecryptPKCS1v15(rand.Reader, priv, data)
}
// 使用系统默认的私钥解密
// @param base64 字符串是否使用base64编码
func DefaultRsaDecrypt(data string, useBase64 bool) (string, error) {
// 空字符串不解密
if data == "" {
return "", nil
}
if useBase64 {
if decodeBase64, err := base64.StdEncoding.DecodeString(data); err != nil {
return "", err
} else {
data = string(decodeBase64)
}
}
priKey, err := GetRsaPrivateKey()
if err != nil {
return "", err
}
val, err := RsaDecrypt(priKey, []byte(data))
if err != nil {
return "", err
}
return string(val), nil
}
// 获取系统的RSA公钥
func GetRsaPublicKey() (string, error) {
if len(RsaPair) == 2 {
return RsaPair[1], nil
}
privateKey, publicKey, err := GenerateRSAKey(1024)
if err != nil {
return "", err
}
RsaPair = append(RsaPair, privateKey)
RsaPair = append(RsaPair, publicKey)
return publicKey, nil
}
// 获取系统私钥
func GetRsaPrivateKey() (string, error) {
if len(RsaPair) == 2 {
return RsaPair[0], nil
}
privateKey, publicKey, err := GenerateRSAKey(1024)
if err != nil {
return "", err
}
RsaPair = append(RsaPair, privateKey)
RsaPair = append(RsaPair, publicKey)
return privateKey, nil
}