From 2bc489cc79e69ebb0cb6b5fd69429abb17f3124a Mon Sep 17 00:00:00 2001 From: lc-1010 <532398960@qq.com> Date: Wed, 28 Jun 2023 15:21:02 +0800 Subject: [PATCH] : Add JWT for authorized token - Add auth table to save JWT secret - Add JWT authorization check in middleware for api #7 --- README.md | 18 ++++++++++-- configs/config.yaml | 5 +++- global/setting.go | 1 + go.mod | 1 + go.sum | 2 ++ internal/dao/auth.go | 8 ++++++ internal/middleware/jwt.go | 46 ++++++++++++++++++++++++++++++ internal/model/auth.go | 31 ++++++++++++++++++++ internal/model/tag.go | 4 +-- internal/routers/api/auth.go | 38 +++++++++++++++++++++++++ internal/routers/api/upload.go | 2 +- internal/routers/router.go | 5 ++++ internal/service/auth.go | 19 +++++++++++++ internal/service/service.go | 2 ++ internal/service/upload.go | 2 +- main.go | 7 +++++ main_test.go | 4 +++ pkg/app/jwt.go | 52 ++++++++++++++++++++++++++++++++++ pkg/errcode/common_code.go | 4 +-- 19 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 internal/dao/auth.go create mode 100644 internal/middleware/jwt.go create mode 100644 internal/model/auth.go create mode 100644 internal/routers/api/auth.go create mode 100644 internal/service/auth.go create mode 100644 pkg/app/jwt.go diff --git a/README.md b/README.md index 1d24108..2e39410 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; ```sql `created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间', `created_by` varchar(100) DEFAULT '' COMMENT '创建人', - `modified_on` int(10) unsigned DEFAULT '0' COMMNET '修改时间', + `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间', `modified_by` varchar(100) DEFAULT '' COMMENT '修改人', `deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间', `is_del` tinyint(3) unsigned DEFAULT '0' COMMENT '是否删除 0 未删 , 1 已删', @@ -61,7 +61,21 @@ CREATE TABLE `blog_article` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章关联标签'; ``` - +- blog_auth +```sql +CREATE TABLE `blog_auth` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `app_key` varchar(20) DEFAULT '' COMMENT 'key', + `app_secret` varchar(50) DEFAULT '' COMMENT 'secret', + `created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间', + `created_by` varchar(100) DEFAULT '' COMMENT '创建人', + `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间', + `modified_by` varchar(100) DEFAULT '' COMMENT '修改人', + `deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint(3) unsigned DEFAULT '0' COMMENT '是否删除 0 未删 , 1 已删', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT="认证管理" +``` ## 建立model diff --git a/configs/config.yaml b/configs/config.yaml index 6ed691f..ce62541 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -27,4 +27,7 @@ Database: ParseTime: True MaxIdleConns: 10 MaxOpenConns: 30 - +JWT: + Secret: 2301a11ba8c311876c058340e7521995 + Issuer: blog-service + Expire: 7200 \ No newline at end of file diff --git a/global/setting.go b/global/setting.go index 4214168..ffd35de 100644 --- a/global/setting.go +++ b/global/setting.go @@ -12,4 +12,5 @@ var ( AppSetting *setting.AppSettingS DatabaseSetting *setting.DatabaseSettingS Logger *logger.Logger + JWTSetting *setting.JWTSettingS ) diff --git a/go.mod b/go.mod index 5b4b7c1..52eb7f4 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/lc-1010/OneBlogService go 1.20 require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-gonic/gin v1.9.1 github.com/go-playground/assert/v2 v2.2.0 github.com/go-playground/locales v0.14.1 diff --git a/go.sum b/go.sum index e401e2b..cd7e56c 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= diff --git a/internal/dao/auth.go b/internal/dao/auth.go new file mode 100644 index 0000000..ace0331 --- /dev/null +++ b/internal/dao/auth.go @@ -0,0 +1,8 @@ +package dao + +import "github.com/lc-1010/OneBlogService/internal/model" + +func (d *Dao) GetAuth(appkey, appSercet string) (model.Auth, error) { + auth := model.Auth{AppKey: appkey, AppSecert: appSercet} + return auth.Get(d.engine) +} diff --git a/internal/middleware/jwt.go b/internal/middleware/jwt.go new file mode 100644 index 0000000..8bc5d05 --- /dev/null +++ b/internal/middleware/jwt.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "github.com/dgrijalva/jwt-go" + "github.com/gin-gonic/gin" + "github.com/lc-1010/OneBlogService/pkg/app" + "github.com/lc-1010/OneBlogService/pkg/errcode" +) + +func JWT() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ( + token string + ecode = errcode.Success + ) + + if s, exist := ctx.GetQuery("token"); exist { + token = s + } else { + token = ctx.GetHeader("token") + } + + if token == "" { + ecode = errcode.InvalidParams + } else { + _, err := app.ParseToken(token) + if err != nil { + switch err.(*jwt.ValidationError).Errors { + case jwt.ValidationErrorExpired: + ecode = errcode.UnauthoerizedTokenTimeout + default: + ecode = errcode.UnauthoerizedTokenError + } + + } + } + if ecode != errcode.Success { + response := app.NewResponse(ctx) + response.ToErrorResponse(ecode) + ctx.Abort() + return + } + + ctx.Next() + } +} diff --git a/internal/model/auth.go b/internal/model/auth.go new file mode 100644 index 0000000..44c4513 --- /dev/null +++ b/internal/model/auth.go @@ -0,0 +1,31 @@ +package model + +import ( + "errors" + + "gorm.io/gorm" +) + +type Auth struct { + *Model + AppKey string `json:"app_key"` + AppSecert string `json:"app_secret"` +} + +func (a Auth) TableName() string { + return "blog_auth" +} + +func (a Auth) Get(db *gorm.DB) (Auth, error) { + var auth Auth + db = db.Where("app_key = ? AND app_secret = ? AND is_del = ?", a.AppKey, a.AppSecert, 0) + err := db.First(&auth).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return auth, nil + } else { + return auth, err + } + } + return auth, nil +} diff --git a/internal/model/tag.go b/internal/model/tag.go index 78bbb9b..ae74ebe 100644 --- a/internal/model/tag.go +++ b/internal/model/tag.go @@ -68,7 +68,7 @@ func (t BlogTag) Create(db *gorm.DB) error { func (t BlogTag) CheckName(db *gorm.DB) (BlogTag, error) { var tag BlogTag err := db.Where("name = ?", t.Name).First(&tag).Error - if err != nil { + if err != nil && err != gorm.ErrRecordNotFound { return tag, err } return tag, nil @@ -93,7 +93,7 @@ func (t BlogTag) Get(db *gorm.DB) (BlogTag, error) { var tag BlogTag err := db.Where("id = ? and is_del = ? and state = ?", t.ID, t.IsDel, t.State).First(&tag).Error - if err != nil { + if err != nil && err != gorm.ErrRecordNotFound { return tag, err } return tag, nil diff --git a/internal/routers/api/auth.go b/internal/routers/api/auth.go new file mode 100644 index 0000000..4c54a10 --- /dev/null +++ b/internal/routers/api/auth.go @@ -0,0 +1,38 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/lc-1010/OneBlogService/global" + "github.com/lc-1010/OneBlogService/internal/service" + "github.com/lc-1010/OneBlogService/pkg/app" + "github.com/lc-1010/OneBlogService/pkg/errcode" +) + +func GetAuth(c *gin.Context) { + param := service.AuthRequest{} + + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.Logger.Errorf(c, "app.BindAndValid errs :%v", errs) + response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...)) + return + } + + svc := service.New(c.Request.Context()) + err := svc.CheckAuth(¶m) + if err != nil { + global.Logger.Errorf(c, "svc.CheckAuth errs :%v", errs) + response.ToErrorResponse(errcode.UnauthorizedAuthNotExist) + return + } + token, err := app.GenerateToken(param.Appkey, param.AppSercet) + if err != nil { + global.Logger.Errorf(c, "svc.GenerateToken errs :%v", errs) + response.ToErrorResponse(errcode.UnauthoerizedTokenGenerate) + return + } + response.ToResponse(gin.H{ + "token": token, + }) +} diff --git a/internal/routers/api/upload.go b/internal/routers/api/upload.go index c886b7b..9d60caf 100644 --- a/internal/routers/api/upload.go +++ b/internal/routers/api/upload.go @@ -17,7 +17,7 @@ func NewUpload() Upload { } func (u Upload) UploadFile(c *gin.Context) { - param := service.UploadParams{ + param := service.UploadRequest{ FormName: "file", FormFileType: "type", } diff --git a/internal/routers/router.go b/internal/routers/router.go index 0e8794f..fa851da 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -48,6 +48,10 @@ func NewRouter() *gin.Engine { upload := api.NewUpload() r.POST("/upload/file", upload.UploadFile) r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath)) + + // auth + r.POST("/auth", api.GetAuth) + //ping test p := r.Group("/test") { @@ -56,6 +60,7 @@ func NewRouter() *gin.Engine { // api router apiv1 := r.Group("/api/v1") + apiv1.Use(middleware.JWT()) { //tags apiv1.POST("/tags", tags.Create) diff --git a/internal/service/auth.go b/internal/service/auth.go new file mode 100644 index 0000000..f381372 --- /dev/null +++ b/internal/service/auth.go @@ -0,0 +1,19 @@ +package service + +import "errors" + +type AuthRequest struct { + Appkey string `form:"app_key" binding:"required"` + AppSercet string `form:"app_sercet" binding:"required"` +} + +func (svc *Service) CheckAuth(param *AuthRequest) error { + auth, err := svc.dao.GetAuth(param.Appkey, param.AppSercet) + if err != nil { + return err + } + if auth.ID > 0 { + return nil + } + return errors.New("auth info does not exist") +} diff --git a/internal/service/service.go b/internal/service/service.go index 652929e..baffbf1 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -7,6 +7,8 @@ import ( "github.com/lc-1010/OneBlogService/internal/dao" ) +// Service +// 使用上下文和dao 的db engien 来处理具体的逻辑 type Service struct { ctx context.Context dao *dao.Dao diff --git a/internal/service/upload.go b/internal/service/upload.go index debe456..6fb4374 100644 --- a/internal/service/upload.go +++ b/internal/service/upload.go @@ -14,7 +14,7 @@ type FileInfo struct { AccessUrl string } -type UploadParams struct { +type UploadRequest struct { FormName string `json:"file,omitempty"` FormFileType string `json:"type,omitempty"` } diff --git a/main.go b/main.go index 402154e..5302f79 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,13 @@ func setupSetting() error { if err != nil { return err } + err = setting.ReadSection("JWT", &global.JWTSetting) + if err != nil { + return err + } + //jwt expire 7200 second + global.JWTSetting.Expire *= time.Second + global.ServerSetting.ReadTimeout *= time.Second global.ServerSetting.WriteTimeout *= time.Second return nil diff --git a/main_test.go b/main_test.go index ccfd975..ba0f434 100644 --- a/main_test.go +++ b/main_test.go @@ -77,3 +77,7 @@ func TestMultiHandler(t *testing.T) { assert.Equal(t, w.Body.String(), exp) } + +func TestMain(t *testing.T) { + main() +} diff --git a/pkg/app/jwt.go b/pkg/app/jwt.go new file mode 100644 index 0000000..a3362b2 --- /dev/null +++ b/pkg/app/jwt.go @@ -0,0 +1,52 @@ +package app + +import ( + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/lc-1010/OneBlogService/global" + "github.com/lc-1010/OneBlogService/pkg/util" +) + +type Claims struct { + AppKey string `json:"app_key"` + AppSecert string `json:"app_secert"` + jwt.StandardClaims +} + +func GetJWTSecret() []byte { + return []byte(global.JWTSetting.Secret) +} + +func GenerateToken(appKey, appSercet string) (string, error) { + nowTime := time.Now() + expireTime := nowTime.Add(global.JWTSetting.Expire) + claims := Claims{ + AppKey: util.EncodeMD5(appKey), + AppSecert: util.EncodeMD5(appSercet), + StandardClaims: jwt.StandardClaims{ + ExpiresAt: expireTime.Unix(), + Issuer: global.JWTSetting.Issuer, + }, + } + tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token, err := tokenClaims.SignedString(GetJWTSecret()) + return token, err +} + +func ParseToken(token string) (*Claims, error) { + tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (any, error) { + return GetJWTSecret(), nil + }) + if err != nil { + return nil, err + } + + if tokenClaims != nil { + Claims, ok := tokenClaims.Claims.(*Claims) + if ok && tokenClaims.Valid { + return Claims, nil + } + } + return nil, err +} diff --git a/pkg/errcode/common_code.go b/pkg/errcode/common_code.go index 685039d..c788b62 100644 --- a/pkg/errcode/common_code.go +++ b/pkg/errcode/common_code.go @@ -29,7 +29,7 @@ var ( ServerError = NewError(1000000, "Server Error") InvalidParams = NewError(1000001, "Invalid Params") NotFound = NewError(1000002, "Not Found") - UnauthoerizedTokeExists = NewError(1000003, "UnauthoerizedTokeExists") + UnauthorizedAuthNotExist = NewError(1000003, "UnauthoerizedTokeExists") UnauthoerizedTokenError = NewError(1000004, "Unauthoerized Token Error") UnauthoerizedTokenTimeout = NewError(1000005, "Unauthoerized Token Timeout") UnauthoerizedTokenGenerate = NewError(1000006, "Unauthoerized Token Generate") @@ -73,7 +73,7 @@ func (e *Error) StatusCode() int { return http.StatusInternalServerError case InvalidParams.Code(): return http.StatusBadRequest - case UnauthoerizedTokeExists.Code(): + case UnauthorizedAuthNotExist.Code(): fallthrough case UnauthoerizedTokenError.Code(): fallthrough