Kratos微服务框架下的认证和鉴权

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Kratos微服务框架下的认证和鉴权,使用JWT实现身份验证(Authentication),使用Casbin实现权鉴(Authorization)

从单体应用到微服务架构,优势很多,但是并不是代表着就没有一点缺点了。

微服务架构,意味着每个服务都是松散耦合的。因此,作为软件工程师和架构师,我们在分布式架构中面临着安全挑战。微服务对外开放的断点,我们称之为:API。

  • 单体应用只需要保护自己就可以了,而微服务的攻击面则很大,这意味着越多的服务将会带来更大的风险,每个服务都得保证其安全性。
  • 在单体架构中,组件之间通过方法来相互调用。而微服务则是依靠开放的API来相互调用,除了要保证其安全性,还得保障其可用性。

因此,根据上述的安全挑战,我们可以得出一个结论:微服务与单体应用有着不同的安全处理方式。


认证和授权的区别

我们在谈论应用程序安全的时候,总是会提到:认证 (Authentication)和 鉴权 (Authorization)这两个术语。但是,总有人很容易混淆概念。

authn_vs_authz.jpg

身份验证(Authentication) 的过程当中,我们需要检查用户的身份以提供对系统的访问。在这个过程当中验证的是 “你是谁?” 。故而,用户需要提供登陆所需的详细信息以供身份的验证。

授权(Authorization) ,是通过了身份验证之后的用户,系统是否授权给他访问特定信息(读)或者执行特定的操作(写)的过程。此过程确定了 用户拥有哪些权限


微服务下的认证和授权策略

我可以想到的解决方案有以下这么几种:

  1. 无API网关 1.1 每个服务各自为政,各自进行认证和鉴权 1.2 拆分出 认证授权服务 进行全局的认证和鉴权
  2. 有API网关 2.1 在网关上进行全局的认证,每个服务各自鉴权 2.2 在网关上进行全局的认证和鉴权 2.3 拆分出 认证服务 进行全局的认证,在网关上进行鉴权

我比较推崇 2.3 这种策略,为什么呢?

  1. 认证对于鉴权来说,是频度较低的服务:登陆不常有,鉴权则发生在每一个API调用上;
  2. 往往认证会相对复杂,具有特异性,难以做到通用化。而鉴权不会特别复杂,容易做到通用化。


有状态和无状态身份验证

当一个设备(客户端)向一个设备(服务端)发送请求的时候,服务端如何判断这个客户端是谁?传统意义上的认证方式又两种:有状态认证无状态认证。有状态认证和无状态认证最大的区别就是服务器会不会保存客户端的信息

有状态身份验证

有状态认证,以cookie-session模型为例,当客户端第一次请求服务端的时候,服务端会返回客户端一个唯一的标识(默认在cookie中),并保存对应的客户端信息,客户端接受到唯一标识之后,将标识保存到本地cookie中,以后的每次请求都携带此cookie,服务器根据此cookie标识就可以判断请求的用户是谁,然后查到对应用户的信息。

无状态身份验证

无状态的认证,客户端在提交身份信息,服务端验证身份后,根据一定的算法生成一个token令牌返回给客户端,之后每次请求服务端,客户端都需要携带此令牌,服务器接受到令牌之后进行校验,校验通过后,提取令牌的信息用来区别用户。


开始实施

在具体的技术选择上:

JWT

在微服务架构下,无状态的身份验证是更为合适的方式,其中以 JWT 为代表,最为流行。

什么是JWT?

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准 RFC 7519 该token被设计为紧凑且安全的,特别适用于分布式站点的 单点登录(SSO) 场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT需要注意的点

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT。
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  4. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  5. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  6. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

Casbin

Casbin 根本上是依托规则引擎做的软件设计,抽取出来的模型可以做到通用化,能够轻松的使用多种不同的控制模型:ACL, RBAC, ABAC等。

什么是Casbin?

Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。目前这个框架的生态已经发展的越来越好了。提供了各种语言的类库,自定义的权限模型语言,以及模型编辑器。

Casbin 可以

  1. 支持自定义请求的格式,默认的请求格式为{subject, object, action}
  2. 具有访问控制模型model和策略policy两个核心概念。
  3. 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
  4. 支持内置的超级用户 例如:rootadministrator。超级用户可以执行任何操作而无需显式的权限声明。
  5. 支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*

Casbin 不能

  1. 身份认证 authentication(即验证用户的用户名和密码),Casbin 只负责访问控制。应该有其他专门的组件负责身份认证,然后由 Casbin 进行访问控制,二者是相互配合的关系。
  2. 管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系。

Kratos

Kratos是B站开源出来的一个微服务框架,我在做技术选型的时候,横向的对比了市面上的主流几款微服务架构,总结下来,还是Kratos更加适合我使用,于是就选择了它。

Kratos的认证和权鉴都是依托中间件来实现的。认证方面,Kratos官方已经支持了Jwt中间件 。鉴权方面,Kratos官方还没有对此的支持,于是我就自己简单的实现了一个Casbin中间件 ,简单封装,足够使用就是了。

实现SecurityUser

SecurityUser用于创建Jwt的令牌,以及后面Casbin解析和存取权鉴相关的数据,需要实现它。

并且实现一个SecurityUserCreator注册进中间件。

const (
ClaimAuthorityId="authorityId")
typeSecurityUserstruct {
PathstringMethodstringAuthorityIdstring}
funcNewSecurityUser() authzM.SecurityUser {
return&SecurityUser{}
}
func (su*SecurityUser) ParseFromContext(ctxcontext.Context) error {
ifclaims, ok :=jwt.FromContext(ctx); ok {
su.AuthorityId=claims.(jwtV4.MapClaims)[ClaimAuthorityId].(string)
    } else {
returnerrors.New("jwt claim missing")
    }
ifheader, ok :=transport.FromServerContext(ctx); ok {
su.Path=header.Operation()
su.Method="*"    } else {
returnerrors.New("jwt claim missing")
    }
returnnil}
func (su*SecurityUser) GetSubject() string {
returnsu.AuthorityId}
func (su*SecurityUser) GetObject() string {
returnsu.Path}
func (su*SecurityUser) GetAction() string {
returnsu.Method}
func (su*SecurityUser) CreateAccessJwtToken(secretKey []byte) string {
claims :=jwtV4.NewWithClaims(jwtV4.SigningMethodHS256,
jwtV4.MapClaims{
ClaimAuthorityId: su.AuthorityId,
    })
signedToken, err :=claims.SignedString(secretKey)
iferr!=nil {
return""    }
returnsignedToken}
func (su*SecurityUser) ParseAccessJwtTokenFromContext(ctxcontext.Context) error {
claims, ok :=jwt.FromContext(ctx)
if!ok {
returnerrors.New("no jwt token in context")
    }
iferr :=su.ParseAccessJwtToken(claims); err!=nil {
returnerr    }
returnnil}
func (su*SecurityUser) ParseAccessJwtTokenFromString(tokenstring, secretKey []byte) error {
parseAuth, err :=jwtV4.Parse(token, func(*jwtV4.Token) (interface{}, error) {
returnsecretKey, nil    })
iferr!=nil {
returnerr    }
claims, ok :=parseAuth.Claims.(jwtV4.MapClaims)
if!ok {
returnerrors.New("no jwt token in context")
    }
iferr :=su.ParseAccessJwtToken(claims); err!=nil {
returnerr    }
returnnil}
func (su*SecurityUser) ParseAccessJwtToken(claimsjwtV4.Claims) error {
ifclaims==nil {
returnerrors.New("claims is nil")
    }
mc, ok :=claims.(jwtV4.MapClaims)
if!ok {
returnerrors.New("claims is not map claims")
    }
strAuthorityId, ok :=mc[ClaimAuthorityId]
ifok {
su.AuthorityId=strAuthorityId.(string)
    }
returnnil}

JWT中间件

创建白名单

在白名单下的API将会被忽略认证和权限验证

需要注意的是:这里面注册的是 操作名(operation),而非是API的URL。具体的操作名是什么,可以在Protoc生成的 *_grpc.pb.go*_http.pb.go 找到。

// NewWhiteListMatcher 创建白名单funcNewWhiteListMatcher() selector.MatchFunc {
whiteList :=make(map[string]bool)
whiteList["/admin.v1.AdminService/Login"] =truereturnfunc(ctxcontext.Context, operationstring) bool {
if_, ok :=whiteList[operation]; ok {
returnfalse        }
returntrue    }
}
创建中间件
// NewMiddleware 创建中间件funcNewMiddleware(loggerlog.Logger) http.ServerOption {
returnhttp.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),
            ),
        ).Match(NewWhiteListMatcher()).Build(),
    )
}
注册中间件
varopts= []http.ServerOption{
NewMiddleware(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{"*"}),
    )),
}
前端发送Token

前端只需要在HTTP的Header里面加入以下数据即可:

Authorization Bearer {JWT Token}
exportdefaultfunctionauthHeader() {
constuserStr=localStorage.getItem("user");
letuser=null;
if (userStr)
user=JSON.parse(userStr);
if (user&&user.token) {
return { Authorization: 'Bearer '+user.token };
  } else {
return {};
  }
}

Casbin中间件

Casbin的模型和策略配置读取,我简化的使用了读取本地配置文件。

通常来说,模型文件变化不大,放本地配置文件或者直接硬代码都没问题。变化的通常都是策略配置,通常做法都是放置在数据库里面,方便通过后台去进行编辑改变。

// NewMiddleware 创建中间件funcNewMiddleware(ac*conf.Auth, loggerlog.Logger) http.ServerOption {
m, _ :=model.NewModelFromFile("../../configs/authz/authz_model.conf")
a :=fileAdapter.NewAdapter("../../configs/authz/authz_policy.csv")
returnhttp.Middleware(
recovery.Recovery(),
tracing.Server(),
logging.Server(logger),
selector.Server(
casbinM.Server(
casbinM.WithCasbinModel(m),
casbinM.WithCasbinPolicy(a),
casbinM.WithSecurityUserCreator(myAuthz.NewSecurityUser),
            ),
        ).Match(NewWhiteListMatcher()).Build(),
    )
}


开始登陆吧

func (s*AdminService) Login(_context.Context, req*v1.LoginReq) (*v1.User, error) {
fmt.Println("Login", req.UserName, req.Password)
variduint64=10varemail="hello@kratos.com"varroles []stringswitchreq.UserName {
case"admin":
roles=append(roles, "ROLE_ADMIN")
case"moderator":
roles=append(roles, "ROLE_MODERATOR")
    }
varsecurityUsermyAuthz.SecurityUsersecurityUser.AuthorityId=req.GetUserName()
token :=securityUser.CreateAccessJwtToken([]byte(s.auth.GetApiKey()))
return&v1.User{
Id:       &id,
UserName: &req.UserName,
Token:    &token,
Email:    &email,
Roles:    roles,
    }, nil}


流程简要说明

  1. 前端发送登陆请求
  2. 登陆请求处理

     2.1 验证用户名密码

     2.2 securityUser.CreateAccessJwtToken生成Jwt的Token

     2.3 返回token给前端

  1. 其他正常的请求

     3.1 Jwt中间件进行令牌进行认证信息校验

     3.2 Casbin中间件解析Jwt中间件的Payload信息,根据用户信息以及操作名进行权鉴。


技术栈

实例代码

目录
相关文章
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
107 3
|
1月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
Dubbo Java 应用服务中间件
微服务框架Dubbo环境部署实战
微服务框架Dubbo环境部署的实战指南,涵盖了Dubbo的概述、服务部署、以及Dubbo web管理页面的部署,旨在指导读者如何搭建和使用Dubbo框架。
229 17
微服务框架Dubbo环境部署实战
|
2月前
|
Kubernetes Java Android开发
用 Quarkus 框架优化 Java 微服务架构的设计与实现
Quarkus 是专为 GraalVM 和 OpenJDK HotSpot 设计的 Kubernetes Native Java 框架,提供快速启动、低内存占用及高效开发体验,显著优化了 Java 在微服务架构中的表现。它采用提前编译和懒加载技术实现毫秒级启动,通过优化类加载机制降低内存消耗,并支持多种技术和框架集成,如 Kubernetes、Docker 及 Eclipse MicroProfile,助力开发者轻松构建强大微服务应用。例如,在电商场景中,可利用 Quarkus 快速搭建商品管理和订单管理等微服务,提升系统响应速度与稳定性。
70 5
|
2月前
|
存储 Java Maven
从零到微服务专家:用Micronaut框架轻松构建未来架构
【9月更文挑战第5天】在现代软件开发中,微服务架构因提升应用的可伸缩性和灵活性而广受欢迎。Micronaut 是一个轻量级的 Java 框架,适合构建微服务。本文介绍如何从零开始使用 Micronaut 搭建微服务架构,包括设置开发环境、创建 Maven 项目并添加 Micronaut 依赖,编写主类启动应用,以及添加控制器处理 HTTP 请求。通过示例代码展示如何实现简单的 “Hello, World!” 功能,并介绍如何通过添加更多依赖来扩展应用功能,如数据访问、验证和安全性等。Micronaut 的强大和灵活性使你能够快速构建复杂的微服务系统。
95 5
|
2月前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
75 2
|
2月前
|
Cloud Native 安全 Java
Micronaut对决Spring Boot:谁是微服务领域的王者?揭秘两者优劣,选对框架至关重要!
【9月更文挑战第5天】近年来,微服务架构备受关注,Micronaut和Spring Boot成为热门选择。Micronaut由OCI开发,基于注解的依赖注入,内置多种特性,轻量级且启动迅速;Spring Boot则简化了Spring应用开发,拥有丰富的生态支持。选择框架需考虑项目需求、团队经验、性能要求及社区支持等因素。希望本文能帮助您选择合适的微服务框架,助力您的软件开发项目取得成功!
162 2
|
3月前
|
Cloud Native JavaScript API
一文读懂云原生 go-zero 微服务框架
一文读懂云原生 go-zero 微服务框架
|
3月前
|
消息中间件 开发框架 Go
【揭秘】如何让Kratos微服务与NATS消息队列完美融合?看完这篇你就懂了!
【8月更文挑战第22天】Kratos是基于Go语言的微服务框架,提供全面工具助力开发者构建高性能应用。NATS作为轻量级消息队列服务,适用于分布式系统消息传递。本文详细介绍如何在Kratos项目中集成NATS,包括创建项目、安装NATS客户端、配置连接、初始化NATS、发送与接收消息等步骤,助您轻松实现高效微服务架构。
68 1
|
3月前
|
JSON 算法 Java
微服务Token鉴权设计的几种方案
【8月更文挑战第18天】在微服务架构中,Token鉴权是确保服务安全性的重要环节。本文将详细介绍几种常见的微服务Token鉴权设计方案,旨在帮助大家在工作和学习中更好地理解和应用这些技术。
153 2