在上一篇文章中,我们了解了kratos框架对于中间件的注册和定义,在每个请求导到接口前都会先顺序执行注册的中间件,接口执行完成后再倒叙执行所有的中间件。
对于登录校验和权限的判断,应该是统一的,可以放到网关或者中间件中去处理,而不是放到接口中,接口只用关注业务逻辑就好了。因此需要在kratos中引入jwt和casbin,注册两个中间件,对请求进行jwt校验和权限校验
// NewWhiteListMatcher 创建casbin白名单
func NewWhiteListMatcher() selector.MatchFunc {
whiteList := make(map[string]struct{})
whiteList["/admin.v1.AdminService/Login"] = struct{}{}
whiteList["/admin.v1.AdminService/Logout"] = struct{}{}
whiteList["/admin.v1.AdminService/Register"] = struct{}{}
whiteList["/admin.v1.AdminService/GetPublicContent"] = struct{}{}
return func(ctx context.Context, operation string) bool {
if _, ok := whiteList[operation]; ok {
fmt.Println("in white list operation:", operation)
return false
}
fmt.Println("not in white list operation:", operation)
return true
}
}
// NewMiddleware 创建中间件
func NewMiddleware(ac *conf.Auth, logger log.Logger) http.ServerOption {
m, _ := model.NewModelFromFile("../../configs/authz/authz_model.conf")
a := fileAdapter.NewAdapter("../../configs/authz/authz_policy.csv")
return http.Middleware(
recovery.Recovery(),
//tracing.Server(),
//logging.Server(logger),
selector.Server(
jwt.Server(
func(token *jwtV4.Token) (interface{}, error) {
return []byte(ac.ApiKey), nil
},
jwt.WithSigningMethod(jwtV4.SigningMethodHS256),
),
casbinM.Server(
casbinM.WithCasbinModel(m),
casbinM.WithCasbinPolicy(a),
casbinM.WithSecurityUserCreator(myAuthz.NewSecurityUser),
),
).Match(NewWhiteListMatcher()).Build(),
)
}
casbin 启动时可以创建对应的白名单,在白名单中的请求不需要校验权限,casbin的权限配置在本地,有对应的账号和对应的角色
// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, ac *conf.Auth, logger log.Logger, s *service.AdminService) *http.Server {
var opts = []http.ServerOption{
NewMiddleware(ac, logger),
http.Filter(handlers.CORS(
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}),
handlers.AllowedOrigins([]string{"*"}),
)),
}
if c.Http.Network != "" {
opts = append(opts, http.Network(c.Http.Network))
}
if c.Http.Addr != "" {
opts = append(opts, http.Address(c.Http.Addr))
}
if c.Http.Timeout != nil {
opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
}
srv := http.NewServer(opts...)
h := openapiv2.NewHandler()
srv.HandlePrefix("/q/", h)
v1.RegisterAdminServiceHTTPServer(srv, s)
return srv
}
注册http服务时将中间件注册到kratos中
jwt和casbin的运行过程
每一次请求,都需要判断path是否在单名单中,若在白名单中,则放行,不进行任何校验操作,比如login接口,一定需要在白名单中,因为这个接口需要生成新的token,生成新的token后,客户端每次请求都可以将token放到header的Authorization字段中,jwt每次都会从请求头中获取这个字段再进行校验
func Server(keyFunc jwt.Keyfunc, opts ...Option) middleware.Middleware {
o := &options{
signingMethod: jwt.SigningMethodHS256,
}
for _, opt := range opts {
opt(o)
}
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
if header, ok := transport.FromServerContext(ctx); ok {
if keyFunc == nil {
return nil, ErrMissingKeyFunc
}
auths := strings.SplitN(header.RequestHeader().Get(authorizationKey), " ", 2)
if len(auths) != 2 || !strings.EqualFold(auths[0], bearerWord) {
return nil, ErrMissingJwtToken
}
jwtToken := auths[1]
var (
tokenInfo *jwt.Token
err error
)
if o.claims != nil {
tokenInfo, err = jwt.ParseWithClaims(jwtToken, o.claims(), keyFunc)
} else {
tokenInfo, err = jwt.Parse(jwtToken, keyFunc)
}
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, ErrTokenInvalid
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
return nil, ErrTokenExpired
} else {
return nil, ErrTokenParseFail
}
}
return nil, errors.Unauthorized(reason, err.Error())
} else if !tokenInfo.Valid {
return nil, ErrTokenInvalid
} else if tokenInfo.Method != o.signingMethod {
return nil, ErrUnSupportSigningMethod
}
ctx = NewContext(ctx, tokenInfo.Claims)
return handler(ctx, req)
}
return nil, ErrWrongContext
}
}
}
jwt校验通过后,需要进行casbin权限校验,权限校验的时候也是先从请求头中拿到token信息,再转换成对应的username(实际上token的生成依赖的是username),然后再拿到请求路径等信息进行校验
// ParseFromContext casbin校验客户端传过来的token
func (su *SecurityUser) ParseFromContext(ctx context.Context) error {
// 从jwt的token中获取用户名
if claims, ok := jwt.FromContext(ctx); ok {
su.AuthorityId = claims.(jwtV4.MapClaims)[ClaimAuthorityId].(string)
fmt.Printf("AuthorityId:%s\n", su.AuthorityId) // 实际上就是请求的username 如zzr
} else {
fmt.Printf("missing claim\n")
return errors.New("jwt claim missing")
}
if header, ok := transport.FromServerContext(ctx); ok {
su.Path = header.Operation()
su.Method = "*"
for _, key := range header.RequestHeader().Keys() {
fmt.Printf("key:%s value:%s\n", key, header.RequestHeader().Get(key))
}
} else {
return errors.New("jwt claim missing")
}
return nil
}
func Server(opts ...Option) middleware.Middleware {
o := &options{
securityUserCreator: nil,
}
for _, opt := range opts {
opt(o)
}
o.enforcer, _ = stdcasbin.NewSyncedEnforcer(o.model, o.policy)
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
if o.enforcer == nil {
return nil, ErrEnforcerMissing
}
if o.securityUserCreator == nil {
return nil, ErrSecurityUserCreatorMissing
}
securityUser := o.securityUserCreator()
if err := securityUser.ParseFromContext(ctx); err != nil {
return nil, ErrSecurityParseFailed
}
ctx = context.WithValue(ctx, SecurityUserContextKey, securityUser)
allowed, err := o.enforcer.Enforce(securityUser.GetSubject(), securityUser.GetObject(), securityUser.GetAction())
if err != nil {
return nil, err
}
if !allowed {
return nil, ErrUnauthorized
}
return handler(ctx, req)
}
}
}
若校验通过,则放行本次请求,否则返回对应的错误码和错误原因
网友评论