Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix]bug #18

Merged
merged 3 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/go-cinch/common/i18n v1.0.6
github.com/go-cinch/common/id v1.0.4
github.com/go-cinch/common/idempotent v1.0.4
github.com/go-cinch/common/jwt v1.0.3
github.com/go-cinch/common/jwt v1.0.4
github.com/go-cinch/common/log v1.1.1
github.com/go-cinch/common/middleware/i18n v1.0.5
github.com/go-cinch/common/middleware/logging v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ github.com/go-cinch/common/id v1.0.4 h1:bMjcxMq5lL19o2a/ty6OnS8tIn7kc98FznAq2vyp
github.com/go-cinch/common/id v1.0.4/go.mod h1:ZeuOCZyx1ZKuCexzLRW0gBCnwDUKH8XDs1S0dNv4Z+k=
github.com/go-cinch/common/idempotent v1.0.4 h1:E/Ab+FHWjs1ym0U0mips6MyHEzucEdedmVWA22U3du8=
github.com/go-cinch/common/idempotent v1.0.4/go.mod h1:t4rBikz+RhpmF0tdGx+rm0Z1FzDy/6GtelgFB7RlaiY=
github.com/go-cinch/common/jwt v1.0.3 h1:/jznvjevW+2KsmWm666O1NxkHLSfVmrjoWwkuWAqewk=
github.com/go-cinch/common/jwt v1.0.3/go.mod h1:++p7kddWh9SyCYIYWEb5mfiR+ljKC3I5puiyjz7oGNs=
github.com/go-cinch/common/jwt v1.0.4 h1:PWLIQ/HaMhQSkyUafBCjQDNXwz1mzeCzsTCbgeKEeRI=
github.com/go-cinch/common/jwt v1.0.4/go.mod h1:++p7kddWh9SyCYIYWEb5mfiR+ljKC3I5puiyjz7oGNs=
github.com/go-cinch/common/log v1.1.1 h1:9ot3Qw4BKDfOZ7IC/3ZreuYJb953YD7pmM6ev6xT9FY=
github.com/go-cinch/common/log v1.1.1/go.mod h1:O4k/ArEdZS6c+YPKdESWJnVfhQ0QlwtIQ6mYxJZt5po=
github.com/go-cinch/common/middleware/i18n v1.0.5 h1:SmxEMljqYF8xraaaa3KZgvNbKIE06ShQAZ34w87wkvs=
Expand Down
4 changes: 2 additions & 2 deletions internal/biz/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ type ActionRepo interface {
Update(ctx context.Context, item *UpdateAction) error
Delete(ctx context.Context, ids ...uint64) error
CodeExists(ctx context.Context, code string) error
Permission(ctx context.Context, code string, req CheckPermission) bool
MatchResource(ctx context.Context, resource string, req CheckPermission) bool
Permission(ctx context.Context, code string, req *CheckPermission) bool
MatchResource(ctx context.Context, resource string, req *CheckPermission) bool
}

type ActionUseCase struct {
Expand Down
4 changes: 2 additions & 2 deletions internal/biz/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type CheckPermission struct {
}

type PermissionRepo interface {
Check(ctx context.Context, item CheckPermission) bool
Check(ctx context.Context, item *CheckPermission) bool
GetByUserCode(ctx context.Context, code string) *Permission
}

Expand All @@ -36,7 +36,7 @@ func NewPermissionUseCase(c *conf.Bootstrap, repo PermissionRepo) *PermissionUse
}
}

func (uc *PermissionUseCase) Check(ctx context.Context, item CheckPermission) (rp bool) {
func (uc *PermissionUseCase) Check(ctx context.Context, item *CheckPermission) (rp bool) {
rp = uc.repo.Check(ctx, item)
return
}
Expand Down
12 changes: 7 additions & 5 deletions internal/biz/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ type UserRepo interface {
Update(ctx context.Context, item *UpdateUser) error
Delete(ctx context.Context, ids ...uint64) error
LastLogin(ctx context.Context, username string) error
WrongPwd(ctx context.Context, req LoginTime) error
WrongPwd(ctx context.Context, req *LoginTime) error
UpdatePassword(ctx context.Context, item *User) error
IdExists(ctx context.Context, id uint64) error
}
Expand Down Expand Up @@ -211,7 +211,7 @@ func (uc *UserUseCase) find(ctx context.Context, action string, condition *FindU

func (uc *UserUseCase) InfoFromCtx(ctx context.Context) (rp *UserInfo) {
user := jwt.FromServerContext(ctx)
return uc.Info(ctx, user.Code)
return uc.Info(ctx, user.Attrs["code"])
}

func (uc *UserUseCase) Info(ctx context.Context, code string) (rp *UserInfo) {
Expand Down Expand Up @@ -259,8 +259,10 @@ func (uc *UserUseCase) Login(ctx context.Context, item *Login) (rp *LoginToken,
return
}
authUser := jwt.User{
Code: status.Code,
Platform: status.Platform,
Attrs: map[string]string{
"code": status.Code,
"platform": status.Platform,
},
}
token, expireTime := authUser.CreateToken(uc.c.Server.Jwt.Key, uc.c.Server.Jwt.Expires)
rp.Token = token
Expand All @@ -279,7 +281,7 @@ func (uc *UserUseCase) LastLogin(ctx context.Context, username string) error {
})
}

func (uc *UserUseCase) WrongPwd(ctx context.Context, req LoginTime) error {
func (uc *UserUseCase) WrongPwd(ctx context.Context, req *LoginTime) error {
return uc.tx.Tx(ctx, func(ctx context.Context) (err error) {
err = uc.repo.WrongPwd(ctx, req)
if err != nil {
Expand Down
5 changes: 2 additions & 3 deletions internal/biz/whitelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
const (
WhitelistPermissionCategory uint32 = iota
WhitelistJwtCategory
WhitelistIdempotentCategory
)

type Whitelist struct {
Expand All @@ -28,8 +27,8 @@ type FindWhitelist struct {
}

type HasWhitelist struct {
Category uint32 `json:"category"`
Permission CheckPermission `json:"permission"`
Category uint32 `json:"category"`
Permission *CheckPermission `json:"permission"`
}

type FindWhitelistCache struct {
Expand Down
6 changes: 3 additions & 3 deletions internal/data/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (ro actionRepo) WordExists(ctx context.Context, word string) (ok bool) {
return
}

func (ro actionRepo) Permission(ctx context.Context, code string, req biz.CheckPermission) (pass bool) {
func (ro actionRepo) Permission(ctx context.Context, code string, req *biz.CheckPermission) (pass bool) {
arr := strings.Split(code, ",")
for _, item := range arr {
pass = ro.permission(ctx, item, req)
Expand All @@ -195,15 +195,15 @@ func (ro actionRepo) Permission(ctx context.Context, code string, req biz.CheckP
return
}

func (ro actionRepo) permission(ctx context.Context, code string, req biz.CheckPermission) (pass bool) {
func (ro actionRepo) permission(ctx context.Context, code string, req *biz.CheckPermission) (pass bool) {
if code == "" {
return
}
action := ro.hotspot.GetActionByCode(ctx, code)
return ro.MatchResource(ctx, action.Resource, req)
}

func (actionRepo) MatchResource(_ context.Context, resource string, req biz.CheckPermission) (pass bool) {
func (actionRepo) MatchResource(_ context.Context, resource string, req *biz.CheckPermission) (pass bool) {
if resource == "" {
// empty resource no need match
return
Expand Down
2 changes: 1 addition & 1 deletion internal/data/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewPermissionRepo(data *Data, action biz.ActionRepo, hotspot biz.HotspotRep
}
}

func (ro permissionRepo) Check(ctx context.Context, item biz.CheckPermission) (pass bool) {
func (ro permissionRepo) Check(ctx context.Context, item *biz.CheckPermission) (pass bool) {
user := ro.hotspot.GetUserByCode(ctx, item.UserCode)
// 1. check default permission
defaultAction := ro.hotspot.GetActionByWord(ctx, "default")
Expand Down
2 changes: 1 addition & 1 deletion internal/data/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func (ro userRepo) LastLogin(ctx context.Context, username string) (err error) {
return
}

func (ro userRepo) WrongPwd(ctx context.Context, req biz.LoginTime) (err error) {
func (ro userRepo) WrongPwd(ctx context.Context, req *biz.LoginTime) (err error) {
oldItem, err := ro.GetByUsername(ctx, req.Username)
if err != nil {
return
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func process(t task) (err error) {
case t.c.Task.Group.LoginFailed:
var req biz.LoginTime
utils.Json2Struct(&req, t.payload.Payload)
err = t.user.WrongPwd(ctx, req)
err = t.user.WrongPwd(ctx, &req)
case t.c.Task.Group.LoginLast:
var req biz.LoginTime
utils.Json2Struct(&req, t.payload.Payload)
Expand Down
178 changes: 178 additions & 0 deletions internal/server/middleware/permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package middleware

import (
"context"
"errors"
"net/http"
"strings"
"time"

"auth/api/auth"
"auth/internal/biz"
"auth/internal/conf"
"github.com/go-cinch/common/copierx"
jwtLocal "github.com/go-cinch/common/jwt"
"github.com/go-cinch/common/log"
"github.com/go-cinch/common/utils"
"github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/transport"
kratosHttp "github.com/go-kratos/kratos/v2/transport/http"
jwtV4 "github.com/golang-jwt/jwt/v4"
"github.com/redis/go-redis/v9"
"google.golang.org/protobuf/types/known/emptypb"
)

const (
pubURIPrefix = "/pub/"
jwtTokenCachePrefix = "jwt.token"
jwtTokenCacheExpire = 10 * time.Minute
permissionHeaderMethod = "x-original-method"
permissionHeaderURI = "x-permission-uri"
)

func Permission(c *conf.Bootstrap, client redis.UniversalClient, whitelist *biz.WhitelistUseCase) func(handler middleware.Handler) middleware.Handler {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (rp interface{}, err error) {
tr, _ := transport.FromServerContext(ctx)
if tr.Kind() == transport.KindGRPC {
// 1. grpc api for internal no need check
// tip: u can add ur logic if need check grpc
return handler(ctx, req)
}
// check http api
var uri string
operation := tr.Operation()
if ht, ok := tr.(kratosHttp.Transporter); ok {
uri = ht.Request().URL.Path
}
// 2. public api no need check
if strings.Contains(uri, pubURIPrefix) {
return handler(ctx, req)
}
if operation == auth.OperationAuthPermission && permissionWhitelist(ctx, whitelist, req) {
// for nginx auth_request
// 3. permission whitelist api no need check
rp = &emptypb.Empty{}
return
} else if jwtWhitelist(ctx, whitelist) {
// 4. jwt whitelist api no need check
return handler(ctx, req)
}
user, err := parseJwt(ctx, c, client, c.Server.Jwt.Key)
if err != nil {
return
}
// pass user info into ctx
ctx = jwtLocal.NewServerContextByUser(ctx, *user)
return handler(ctx, req)
}
}
}

func permissionWhitelist(ctx context.Context, whitelist *biz.WhitelistUseCase, req interface{}) (ok bool) {
tr, _ := transport.FromServerContext(ctx)
var r biz.CheckPermission
copierx.Copy(&r, req)
// get from header if exist
method := tr.RequestHeader().Get(permissionHeaderMethod)
if method != "" {
r.Method = method
}
uri := tr.RequestHeader().Get(permissionHeaderURI)
if uri != "" {
r.URI = uri
}
// public api no need check
if strings.Contains(r.URI, pubURIPrefix) {
return true
}
log.
WithContext(ctx).
Info("method: %s, uri: %s, resource: %s", r.Method, r.URI, r.Resource)
// skip options
if r.Method == http.MethodOptions || r.Method == http.MethodHead {
return
}
// check if it is on the whitelist
ok = whitelist.Has(ctx, &biz.HasWhitelist{
Category: biz.WhitelistPermissionCategory,
Permission: &r,
})
// override params
v, ok2 := req.(*auth.PermissionRequest)
if ok2 {
v.Method = &r.Method
v.Uri = &r.URI
req = v
return
}
return
}

func jwtWhitelist(ctx context.Context, whitelist *biz.WhitelistUseCase) bool {
tr, _ := transport.FromServerContext(ctx)
return whitelist.Has(ctx, &biz.HasWhitelist{
Category: biz.WhitelistJwtCategory,
Permission: &biz.CheckPermission{
Resource: tr.Operation(),
},
})
}

func parseJwt(ctx context.Context, c *conf.Bootstrap, client redis.UniversalClient, jwtKey string) (user *jwtLocal.User, err error) {
user = jwtLocal.FromServerContext(ctx)
if user.Token == "" {
err = biz.ErrJwtMissingToken(ctx)
return
}
key := strings.Join([]string{c.Name, jwtTokenCachePrefix, utils.StructMd5(user.Token)}, ".")
res, _ := client.Get(ctx, key).Result()
if res != "" {
utils.Json2Struct(user, res)
return
}

// parse Authorization jwt token to get user info
var info *jwtV4.Token
info, err = parseToken(ctx, jwtKey, user.Token)
if err != nil {
return
}
ctx = jwtLocal.NewServerContext(ctx, info.Claims, "code", "platform")
user = jwtLocal.FromServerContext(ctx)
client.Set(ctx, key, utils.Struct2Json(user), jwtTokenCacheExpire)
return
}

func parseToken(ctx context.Context, key, jwtToken string) (info *jwtV4.Token, err error) {
info, err = jwtV4.Parse(jwtToken, func(token *jwtV4.Token) (rp interface{}, err error) {
rp = []byte(key)
return
})
if err != nil {
var ve *jwtV4.ValidationError
ok := errors.As(err, &ve)
if !ok {
return
}
if ve.Errors&jwtV4.ValidationErrorMalformed != 0 {
err = biz.ErrJwtTokenInvalid(ctx)
return
}
if ve.Errors&(jwtV4.ValidationErrorExpired|jwtV4.ValidationErrorNotValidYet) != 0 {
err = biz.ErrJwtTokenExpired(ctx)
return
}
err = biz.ErrJwtTokenParseFail(ctx)
return
}
if !info.Valid {
err = biz.ErrJwtTokenParseFail(ctx)
return
}
if info.Method != jwtV4.SigningMethodHS512 {
err = biz.ErrJwtUnSupportSigningMethod(ctx)
return
}
return
}
16 changes: 9 additions & 7 deletions internal/service/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ func (s *AuthService) Permission(ctx context.Context, req *auth.PermissionReques
defer span.End()
rp = &emptypb.Empty{}
user := jwt.FromServerContext(ctx)
r := biz.CheckPermission{
UserCode: user.Code,
r := &biz.CheckPermission{
UserCode: user.Attrs["code"],
}
if req.Resource != nil {
r.Resource = *req.Resource
Expand All @@ -156,10 +156,12 @@ func (s *AuthService) Permission(ctx context.Context, req *auth.PermissionReques
err = biz.ErrNoPermission(ctx)
return
}
info := s.user.Info(ctx, user.Code)
info := s.user.Info(ctx, user.Attrs["code"])
jwt.AppendToReplyHeader(ctx, jwt.User{
Code: info.Code,
Platform: info.Platform,
Attrs: map[string]string{
"code": info.Code,
"platform": info.Platform,
},
})
return
}
Expand All @@ -171,8 +173,8 @@ func (s *AuthService) Info(ctx context.Context, _ *emptypb.Empty) (rp *auth.Info
rp = &auth.InfoReply{}
rp.Permission = &auth.Permission{}
user := jwt.FromServerContext(ctx)
res := s.user.Info(ctx, user.Code)
permission := s.permission.GetByUserCode(ctx, user.Code)
res := s.user.Info(ctx, user.Attrs["code"])
permission := s.permission.GetByUserCode(ctx, user.Attrs["code"])
copierx.Copy(&rp.Permission, permission)
copierx.Copy(&rp, res)
return
Expand Down
Loading