diff --git a/configs/configs.go b/configs/configs.go index 2706051..372963e 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -54,15 +54,10 @@ type Config struct { ExpireDuration time.Duration `toml:"expireDuration"` } `toml:"jwt"` - Aes struct { - Key string `toml:"key"` - Iv string `toml:"iv"` - } `toml:"aes"` - - Rsa struct { - Private string `toml:"private"` - Public string `toml:"public"` - } `toml:"rsa"` + URLToken struct { + Secret string `toml:"secret"` + ExpireDuration time.Duration `toml:"expireDuration"` + } `toml:"urlToken"` Cmd struct { GenTables string `toml:"genTables"` diff --git a/configs/fat_configs.toml b/configs/fat_configs.toml index 6474b43..8fe084b 100644 --- a/configs/fat_configs.toml +++ b/configs/fat_configs.toml @@ -34,48 +34,9 @@ to = "" # 收件人邮箱,多个可以逗号(,)分割 secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时) -[aes] -key = 'IgkibX71IEf382PT' -iv = 'IgkibX71IEf382PT' - -[rsa] -private = '-----BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0 -IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q -vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND -VSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e9Tn8f99M2VMqpzXHXZzv1ABmqufz -S20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HMxLYEFN9h2MWYgxLm9Z0rLMrWwMM+ -E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbzowIDAQABAoIBAQCO1RE1ItUlO6kj -Un0ENAgEqojAUqGvsT33Yo7kAZO+/cOeb0UEqk0iq5bf7L9ncBynWDg6ZPc6X3/g -wdFdKxAvHck9zjM3VL+EMP+bNyrR0K8ZYk5Kx+Q/PEK+Mp8dfRdgggAUsZaNWB+a -rVVspiMo1wo28KBl5x8NevTnJkOLqXAyB7UyLWqnOL1fb988lZvZPR7ZUYroVIZa -pyXtZcafIJeKyQ3bvWI5+eFqOe61Z4Bx1+TpfZ3fKfSDW0vhxzNqaimOa8jSXtMJ -jMeOctL4nZ0TPo/jS3I+XlaH4ZQlFLuUWGscpxwfEeBN23I8HRLkZXJsw66yvRN3 -s4bUKPXRAoGBAP/3oSZAECvfsYYzs76tnrAmR/0GxCqgguxDlWn5DowQzdWFOdHC -ZbTo/hUVoMSQnO1EKCFlnBS+wg/3TuIzUO0ewC1aeT7qHbOMDl0zKbNpS2Z9/j+U -zro+qz7XmkWolMCfmDrCrw9CtCxcMSII+ajbI8SAgFVMz9XnDt+xW9E9AoGBANT0 -4F6kCUJTEyqf2+v84tjQ2wGIF6XtZPU9JR806zeMyahQ9F6z3hY8BYb0tIy5b3uJ -VlJ9TG1qg/t59TWxIq43mYSUJHe0aJi3ilooObQtHlhPu8nwmmX47sX0PyG2hMoD -kBVxTpTDmBaDz7O9uBnlMXJN5qEygctaixpEbmZfAoGBAMBA9kEMjRjnAyeRXcgy -D6aumhNqKZz6wltCx864yjxZwsBFOJBcOpgPCAg+HmqFU9jCAIJVF05dmNT1I8Ky -WG5BUoa+FaMzpOtenstRylh/Far9pyGKW1t4BpdEyRLY9CFZvbUk1OfZagqHlD/E -DgDN16eX/MwUzWYUDg/l3tjhAoGBAKGip/ZNjVWRFpggs9z/mfK1O7WC5Wgksp9N -ZLK2CN6l9p3RrFmBLk00C4HulGfHi+15RVLhFbRqx3iFje/N3iPbwaMWikNtZIKd -tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F -4FT0odMDAoGBAJk3ELB/FHY8xzZ4jF1wG/a1CK681Xm6SuU5KIELDSAUNoou6OPG -mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr -QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV ------END RSA PRIVATE KEY-----' - -public = '-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i -zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo -m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3 -4GGRdrSJ+m4iVk/Kt95tavBNk+NDVSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e -9Tn8f99M2VMqpzXHXZzv1ABmqufzS20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HM -xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz -owIDAQAB ------END PUBLIC KEY-----' +[urlToken] +secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret +expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟) [cmd] genTables = 'user_demo' \ No newline at end of file diff --git a/internal/api/code/code.go b/internal/api/code/code.go index 797d7b6..3efbeb1 100644 --- a/internal/api/code/code.go +++ b/internal/api/code/code.go @@ -13,6 +13,8 @@ const ( ParamBindError = 10103 AuthorizationError = 10104 CallHTTPError = 10105 + ResubmitError = 10106 + ResubmitMsg = 10107 // 模块级错误码 - 用户模块 IllegalUserName = 20101 @@ -29,6 +31,8 @@ var codeText = map[int]string{ ParamBindError: "参数信息有误", AuthorizationError: "签名信息有误", CallHTTPError: "调用第三方 HTTP 接口失败", + ResubmitError: "Resubmit Error", + ResubmitMsg: "请勿重复提交", IllegalUserName: "非法用户名", UserCreateError: "创建用户失败", diff --git a/internal/api/controller/demo_handler/func_auth.go b/internal/api/controller/demo_handler/func_auth.go index 1f78506..60a9f07 100644 --- a/internal/api/controller/demo_handler/func_auth.go +++ b/internal/api/controller/demo_handler/func_auth.go @@ -28,7 +28,7 @@ type authResponse struct { func (h *handler) Auth() core.HandlerFunc { return func(c core.Context) { cfg := configs.Get().JWT - tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration) + tokenString, err := token.New(cfg.Secret).JwtSign(1, "xinliangnote", time.Hour*cfg.ExpireDuration) if err != nil { c.AbortWithError(errno.NewError( http.StatusBadRequest, diff --git a/internal/api/router/middleware/auth/auth.go b/internal/api/router/middleware/middle_jwt.go similarity index 85% rename from internal/api/router/middleware/auth/auth.go rename to internal/api/router/middleware/middle_jwt.go index 9ac01df..162f327 100644 --- a/internal/api/router/middleware/auth/auth.go +++ b/internal/api/router/middleware/middle_jwt.go @@ -1,4 +1,4 @@ -package auth +package middleware import ( "net/http" @@ -12,7 +12,7 @@ import ( "github.com/pkg/errors" ) -func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) { +func (m *middleware) Jwt(ctx core.Context) (userId int64, userName string, err errno.Error) { auth := ctx.GetHeader("Authorization") if auth == "" { err = errno.NewError( @@ -24,7 +24,7 @@ func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Err } cfg := configs.Get().JWT - claims, errParse := token.New(cfg.Secret).Parse(auth) + claims, errParse := token.New(cfg.Secret).JwtParse(auth) if errParse != nil { err = errno.NewError( http.StatusUnauthorized, diff --git a/internal/api/router/middleware/middle_resubmit.go b/internal/api/router/middleware/middle_resubmit.go new file mode 100644 index 0000000..ec3b4b8 --- /dev/null +++ b/internal/api/router/middleware/middle_resubmit.go @@ -0,0 +1,70 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/api/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/cache" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/errno" + "github.com/xinliangnote/go-gin-api/pkg/token" + + "github.com/pkg/errors" +) + +func (m *middleware) Resubmit() core.HandlerFunc { + + redisKeyPrefix := configs.ProjectName() + ":request-id:" + + return func(c core.Context) { + cfg := configs.Get().URLToken + + tokenString, err := token.New(cfg.Secret).UrlSign(c.Path(), c.Method(), c.RequestInputParams()) + if err != nil { + c.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ResubmitError, + code.Text(code.ResubmitError)).WithErr(err), + ) + return + } + + redisKey := redisKeyPrefix + tokenString + if !m.cache.Exists(redisKey) { + err = m.cache.Set(redisKey, "1", time.Minute*cfg.ExpireDuration) + if err != nil { + c.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ResubmitError, + code.Text(code.ResubmitError)).WithErr(err), + ) + return + } + + return + } + + redisValue, err := m.cache.Get(redisKey, cache.WithTrace(c.Trace())) + if err != nil { + c.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ResubmitError, + code.Text(code.ResubmitError)).WithErr(err), + ) + return + } + + if redisValue == "1" { + c.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ResubmitMsg, + code.Text(code.ResubmitMsg)).WithErr(errors.New("resubmit")), + ) + return + } + + return + } +} diff --git a/internal/api/router/middleware/middlerware.go b/internal/api/router/middleware/middlerware.go new file mode 100644 index 0000000..e4c363c --- /dev/null +++ b/internal/api/router/middleware/middlerware.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/service/user_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/cache" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/grpc" + "github.com/xinliangnote/go-gin-api/pkg/errno" + + "go.uber.org/zap" +) + +var _ Middleware = (*middleware)(nil) + +type Middleware interface { + // i 为了避免被其他包实现 + i() + + // JWT 中间件 + Jwt(ctx core.Context) (userId int64, userName string, err errno.Error) + + // Resubmit 中间件 + Resubmit() core.HandlerFunc +} + +type middleware struct { + logger *zap.Logger + cache cache.Repo + grpConn grpc.ClientConn + userService user_service.UserService +} + +func New(logger *zap.Logger, cache cache.Repo) Middleware { + return &middleware{ + logger: logger, + cache: cache, + } +} + +func (m *middleware) i() {} diff --git a/internal/api/router/router.go b/internal/api/router/router.go index bee5317..8b76b11 100644 --- a/internal/api/router/router.go +++ b/internal/api/router/router.go @@ -3,7 +3,7 @@ package router import ( "github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler" "github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler" - "github.com/xinliangnote/go-gin-api/internal/api/router/middleware/auth" + "github.com/xinliangnote/go-gin-api/internal/api/router/middleware" "github.com/xinliangnote/go-gin-api/internal/graph/handler" "github.com/xinliangnote/go-gin-api/internal/pkg/cache" "github.com/xinliangnote/go-gin-api/internal/pkg/core" @@ -33,6 +33,10 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C panic(err) } + // 中间件 + middles := middleware.New(logger, cache) + + // graphQL 控制器 gqlHandler := handler.New(logger, db, cache) gql := mux.Group("/graphql") @@ -41,11 +45,26 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C gql.POST("/query", gqlHandler.Query()) } + // demo 控制器 demoHandler := demo_handler.New(logger, db, cache, grpConn) - userHandler := user_handler.New(logger, db, cache) + demo := mux.Group("/demo", core.WrapAuthHandler(middles.Jwt)) // 使用 jwt 验证 + { + // 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。 + demo.GET("/trace", demoHandler.Trace()) - // user_demo CURD - user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler)) + // 模拟数据 + demo.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get()) + demo.POST("post", demoHandler.Post()) + } + + demoNoAuth := mux.Group("/auth") // 不使用 jwt 验证 + { + demoNoAuth.POST("/get", demoHandler.Auth()) + } + + // user 控制器 + userHandler := user_handler.New(logger, db, cache) + user := mux.Group("/user", core.WrapAuthHandler(middles.Jwt)) { user.POST("/create", userHandler.Create()) user.PUT("/update", userHandler.UpdateNickNameByID()) @@ -53,22 +72,5 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail()) } - // auth - a := mux.Group("/auth") - { - a.POST("/get", demoHandler.Auth()) - } - - // demo - d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证 - { - // 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。 - d.GET("/trace", demoHandler.Trace()) - - // 模拟数据 - d.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get()) - d.POST("post", demoHandler.Post()) - } - return mux, nil } diff --git a/internal/api/service/user_service/func_create.go b/internal/api/service/user_service/service_create.go similarity index 100% rename from internal/api/service/user_service/func_create.go rename to internal/api/service/user_service/service_create.go diff --git a/internal/api/service/user_service/func_delete.go b/internal/api/service/user_service/service_delete.go similarity index 100% rename from internal/api/service/user_service/func_delete.go rename to internal/api/service/user_service/service_delete.go diff --git a/internal/api/service/user_service/func_getuserbyusername.go b/internal/api/service/user_service/service_getuserbyusername.go similarity index 100% rename from internal/api/service/user_service/func_getuserbyusername.go rename to internal/api/service/user_service/service_getuserbyusername.go diff --git a/internal/api/service/user_service/func_updatenicknamebyid.go b/internal/api/service/user_service/service_updatenicknamebyid.go similarity index 100% rename from internal/api/service/user_service/func_updatenicknamebyid.go rename to internal/api/service/user_service/service_updatenicknamebyid.go diff --git a/internal/pkg/cache/redis.go b/internal/pkg/cache/redis.go index 995bc17..379540f 100644 --- a/internal/pkg/cache/redis.go +++ b/internal/pkg/cache/redis.go @@ -34,6 +34,7 @@ type Repo interface { Expire(key string, ttl time.Duration) bool ExpireAt(key string, ttl time.Time) bool Del(keys ...string) bool + Exists(keys ...string) bool Incr(key string, options ...Option) int64 Close() error } @@ -148,6 +149,15 @@ func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool { return ok } +// +func (c *cacheRepo) Exists(keys ...string) bool { + if len(keys) == 0 { + return true + } + value, _ := c.client.Exists(keys...).Result() + return value > 0 +} + // Del del some key from redis func (c *cacheRepo) Del(keys ...string) bool { if len(keys) == 0 { diff --git a/internal/pkg/core/context.go b/internal/pkg/core/context.go index e331f75..0cad774 100644 --- a/internal/pkg/core/context.go +++ b/internal/pkg/core/context.go @@ -120,6 +120,10 @@ type Context interface { Alias() string setAlias(path string) + // RequestInputParams 获取所有参数 + RequestInputParams() url.Values + // RequestPostFormParams 获取 PostForm 参数 + RequestPostFormParams() url.Values // Request 获取 Request 对象 Request() *http.Request // RawData 获取 Request.Body @@ -326,6 +330,18 @@ func (c *context) setAlias(path string) { } } +// RequestInputParams 获取所有参数 +func (c *context) RequestInputParams() url.Values { + _ = c.ctx.Request.ParseForm() + return c.ctx.Request.Form +} + +// RequestPostFormParams 获取 PostForm 参数 +func (c *context) RequestPostFormParams() url.Values { + _ = c.ctx.Request.ParseForm() + return c.ctx.Request.PostForm +} + // Request 获取 Request func (c *context) Request() *http.Request { return c.ctx.Request diff --git a/pkg/token/README.md b/pkg/token/README.md new file mode 100644 index 0000000..0b25585 --- /dev/null +++ b/pkg/token/README.md @@ -0,0 +1,20 @@ +## 与 UrlSign 对应的 PHP 加密算法 + +```php +// 对 params key 进行排序 +ksort($params); + +// 对 sortParams 进行 Encode +$sortParamsEncode = http_build_query($params); + +// 加密字符串规则 path + method + sortParamsEncode + secret +$encryptStr = $path . $method . $sortParamsEncode . $secret + +// 对加密字符串进行 md5 +$md5Str = md5($encryptStr); + +// 对 md5Str 进行 base64 encode +$tokenString = base64_encode($md5Str); + +echo $tokenString; +``` \ No newline at end of file diff --git a/pkg/token/token.go b/pkg/token/token.go index bdd6dd2..73dc07c 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -1,6 +1,7 @@ package token import ( + "net/url" "time" "github.com/dgrijalva/jwt-go" @@ -11,8 +12,13 @@ var _ Token = (*token)(nil) type Token interface { // i 为了避免被其他包实现 i() - Sign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) - Parse(tokenString string) (*claims, error) + + // JWT 签名方式 + JwtSign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) + JwtParse(tokenString string) (*claims, error) + + // URL 签名方式,不支持解密 + UrlSign(path string, method string, params url.Values) (tokenString string, err error) } type token struct { @@ -32,39 +38,3 @@ func New(secret string) Token { } func (t *token) i() {} - -func (t *token) Sign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) { - // The token content. - // iss: (Issuer)签发者 - // iat: (Issued At)签发时间,用Unix时间戳表示 - // exp: (Expiration Time)过期时间,用Unix时间戳表示 - // aud: (Audience)接收该JWT的一方 - // sub: (Subject)该JWT的主题 - // nbf: (Not Before)不要早于这个时间 - // jti: (JWT ID)用于标识JWT的唯一ID - claims := claims{ - userId, - userName, - jwt.StandardClaims{ - NotBefore: time.Now().Unix(), - IssuedAt: time.Now().Unix(), - ExpiresAt: time.Now().Add(expireDuration).Unix(), - }, - } - tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(t.secret)) - return -} - -func (t *token) Parse(tokenString string) (*claims, error) { - tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) { - return []byte(t.secret), nil - }) - - if tokenClaims != nil { - if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid { - return claims, nil - } - } - - return nil, err -} diff --git a/pkg/token/token_jwt.go b/pkg/token/token_jwt.go new file mode 100644 index 0000000..0e67291 --- /dev/null +++ b/pkg/token/token_jwt.go @@ -0,0 +1,43 @@ +package token + +import ( + "time" + + "github.com/dgrijalva/jwt-go" +) + +func (t *token) JwtSign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) { + // The token content. + // iss: (Issuer)签发者 + // iat: (Issued At)签发时间,用Unix时间戳表示 + // exp: (Expiration Time)过期时间,用Unix时间戳表示 + // aud: (Audience)接收该JWT的一方 + // sub: (Subject)该JWT的主题 + // nbf: (Not Before)不要早于这个时间 + // jti: (JWT ID)用于标识JWT的唯一ID + claims := claims{ + userId, + userName, + jwt.StandardClaims{ + NotBefore: time.Now().Unix(), + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(expireDuration).Unix(), + }, + } + tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(t.secret)) + return +} + +func (t *token) JwtParse(tokenString string) (*claims, error) { + tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(t.secret), nil + }) + + if tokenClaims != nil { + if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid { + return claims, nil + } + } + + return nil, err +} diff --git a/pkg/token/token_test.go b/pkg/token/token_test.go index b439710..46f87cc 100644 --- a/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -1,14 +1,15 @@ package token import ( + "net/url" "testing" "time" ) const secret = "i1ydX9RtHyuJTrw7frcu" -func TestSign(t *testing.T) { - tokenString, err := New(secret).Sign(123456789, "xinliangnote", 24*time.Hour) +func TestJwtSign(t *testing.T) { + tokenString, err := New(secret).JwtSign(123456789, "xinliangnote", 24*time.Hour) if err != nil { t.Error("sign error", err) return @@ -16,9 +17,9 @@ func TestSign(t *testing.T) { t.Log(tokenString) } -func TestParse(t *testing.T) { +func TestJwtParse(t *testing.T) { tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1Njc4OSwidXNlcm5hbWUiOiJ4aW5saWFuZyIsImV4cCI6MTYwOTQ2NzcwNCwiaWF0IjoxNjA5MzgxMzA0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjA5MzgxMzA0fQ.hccv8F713WpKcwiSldBrFLZz_2SZzOTPedPi-8ps7M4" - user, err := New(secret).Parse(tokenString) + user, err := New(secret).JwtParse(tokenString) if err != nil { t.Error("parse error", err) return @@ -26,11 +27,28 @@ func TestParse(t *testing.T) { t.Log(user) } -func BenchmarkSignAndParse(b *testing.B) { +func TestUrlSign(t *testing.T) { + + urlPath := "/echo" + method := "post" + params := url.Values{} + params.Add("a", "a1") + params.Add("d", "d1") + params.Add("c", "c1") + + tokenString, err := New(secret).UrlSign(urlPath, method, params) + if err != nil { + t.Error("sign error", err) + return + } + t.Log(tokenString) +} + +func BenchmarkJwtSignAndParse(b *testing.B) { b.ResetTimer() token := New(secret) for i := 0; i < b.N; i++ { - tokenString, _ := token.Sign(123456789, "xinliangnote", 24*time.Hour) - token.Parse(tokenString) + tokenString, _ := token.JwtSign(123456789, "xinliangnote", 24*time.Hour) + token.JwtParse(tokenString) } } diff --git a/pkg/token/token_url.go b/pkg/token/token_url.go new file mode 100644 index 0000000..de1ffca --- /dev/null +++ b/pkg/token/token_url.go @@ -0,0 +1,48 @@ +package token + +import ( + "crypto/md5" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "net/url" + "strings" +) + +// UrlSign +// path 请求的路径 (不附带 querystring) +func (t *token) UrlSign(path string, method string, params url.Values) (tokenString string, err error) { + // 合法的 Methods + methods := map[string]bool{ + "get": true, + "post": true, + "put": true, + "path": true, + "delete": true, + "head": true, + "options": true, + } + + methodName := strings.ToLower(method) + if !methods[methodName] { + err = errors.New("method param error") + return + } + + // Encode() 方法中自带 sorted by key + sortParamsEncode := params.Encode() + + // 加密字符串规则 path + method + sortParamsEncode + secret + encryptStr := fmt.Sprintf("%s%s%s%s", path, methodName, sortParamsEncode, t.secret) + + // 对加密字符串进行 md5 + s := md5.New() + s.Write([]byte(encryptStr)) + md5Str := hex.EncodeToString(s.Sum(nil)) + + // 对 md5Str 进行 base64 encode + tokenString = base64.StdEncoding.EncodeToString([]byte(md5Str)) + + return +} diff --git a/pkg/utils/sql/sql.go b/pkg/utils/sql/sql.go deleted file mode 100644 index 99b64a4..0000000 --- a/pkg/utils/sql/sql.go +++ /dev/null @@ -1,14 +0,0 @@ -package sql - -import "strings" - -// EscapeString 转义 SQL 语句中使用的字符串中的特殊字符 -func EscapeString(value string) string { - replace := map[string]string{"\\": "\\\\", "'": `\'`, "\\0": "\\\\0", "\n": "\\n", "\r": "\\r", `"`: `\"`, "\x1a": "\\Z"} - - for b, a := range replace { - value = strings.Replace(value, b, a, -1) - } - - return value -} diff --git a/pkg/utils/sql/sql_test.go b/pkg/utils/sql/sql_test.go deleted file mode 100644 index f43f0e3..0000000 --- a/pkg/utils/sql/sql_test.go +++ /dev/null @@ -1,8 +0,0 @@ -package sql - -import "testing" - -func TestEscapeString(t *testing.T) { - str := "' OR ''=' union select 1,database(),2#" - t.Log(EscapeString(str)) -}