微服务架构下的鉴权,怎么做更优雅?
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | code301
来源 | urlify.cn/INBnEj
一、单体应用 VS 微服务
二、微服务常见安全认证方案
HTTP 基本认证
客户端发送 HTTP Request 给服务器。
因为 Request 中没有包含 Authorization header,服务器会返回一个 401 Unauthozied 给客户端,并且在 Response 的 Header "WWW-Authenticate" 中添加信息。
客户端把用户名和密码用 BASE64 加密后,放在 Authorization Header 中发送给服务器, 认证成功。
服务器将 Authorization Header 中的用户名密码取出,进行验证, 如果验证通过,将根据请求,发送资源给客户端。
基于 Session 的认证
基于 Session 的认证应该是最常用的一种认证机制了。用户登录认证成功后,将用户相关数据存储到 Session 中,单体应用架构中,默认 Session 会存储在应用服务器中,并且将 Session ID 返回到客户端,存储在浏览器的 Cookie 中。
但是在分布式架构下,Session 存放于某个具体的应用服务器中自然就无法满足使用了,简单的可以通过 Session 复制或者 Session 粘制的方案来解决。
Session 复制依赖于应用服务器,需要应用服务器有 Session 复制能力,不过现在大部分应用服务器如 Tomcat、JBoss、WebSphere 等都已经提供了这个能力。
除此之外,Session 复制的一大缺陷在于当节点数比较多时,大量的 Session 数据复制会占用较多网络资源。Session 粘滞是通过负载均衡器,将统一用户的请求都分发到固定的服务器节点上,这样就保证了对某一用户而言,Session 数据始终是正确的。不过这种方案依赖于负载均衡器,并且只能满足水平扩展的集群场景,无法满足应用分割后的分布式场景。
在微服务架构下,每个微服务拆分的粒度会很细,并且不只有用户和微服务打交道,更多还有微服务间的调用。这个时候上述两个方案都无法满足,就要求必须要将 Session 从应用服务器中剥离出来,存放在外部进行集中管理。可以是数据库,也可以是分布式缓存,如 Memchached、Redis 等。这正是 David Borsos 建议的第二种方案,分布式 Session 方案。
基于 Token 的认证
用户输入登录信息(或者调用 Token 接口,传入用户信息),发送到身份认证服务进行认证(身份认证服务可以和服务端在一起,也可以分离,看微服务拆分情况了)。
身份验证服务验证登录信息是否正确,返回接口(一般接口中会包含用户基础信息、权限范围、有效时间等信息),客户端存储接口,可以存储在 Session 或者数据库中。
用户将 Token 放在 HTTP 请求头中,发起相关 API 调用。
被调用的微服务,验证 Token 权限。
服务端返回相关资源和数据。
服务端无状态:Token 机制在服务端不需要存储 session 信息,因为 Token 自身包含了所有用户的相关信息。
性能较好,因为在验证 Token 时不用再去访问数据库或者远程服务进行权限校验,自然可以提升不少性能。
支持移动设备。
支持跨程序调用,Cookie 是不允许垮域访问的,而 Token 则不存在这个问题。
三、JWT 介绍
简介
JWT 认证流程
客户端调用登录接口(或者获取 token 接口),传入用户名密码。
服务端请求身份认证中心,确认用户名密码正确。
服务端创建 JWT,返回给客户端。
客户端拿到 JWT,进行存储(可以存储在缓存中,也可以存储在数据库中,如果是浏览器,可以存储在 Cookie 中)在后续请求中,在 HTTP 请求头中加上 JWT。
服务端校验 JWT,校验通过后,返回相关资源和数据。
JWT 结构
header.payload.signature
{
"type" : "JWT",
"ALG" : "HS256"
}
标准中注册的声明
公共的声明
私有的声明
iss:JWT 签发者
sub:JWT 所面向的用户
aud:接收 JWT 的一方
exp:JWT 的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之前,该 JWT 都是不可用的
iat:JWT 的签发时间
jti:JWT 的唯一身份标识,主要用来作为一次性 token, 从而回避重放攻击。
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。
跨语言,JSON 的格式保证了跨语言的支撑
基于 Token,无状态
占用字节小,便于传输
Token 存储在 Cookie 中,这样客户端注销时,自然可以清空掉
注销时,将 Token 存放到分布式缓存中,每次校验 Token 时区检查下该 Token 是否已注销。不过这样也就失去了快速校验 Token 的优点。
多采用短期令牌,比如令牌有效期是 20 分钟,这样可以一定程度上降低注销后 Token 可用性的风险。
四、OAuth 2.0 介绍
简单:不管是 OAuth 服务提供者还是应用开发者,都很容易于理解与使用;
安全:没有涉及到用户密钥等信息,更安全更灵活;
开放:任何服务提供商都可以实现 OAuth,任何软件开发商都可以使用 OAuth;
授权流程
客户端:客户端是代表资源所有者对资源服务器发出访问受保护资源请求的应用程序。
资源拥有者:资源拥有者是对资源具有授权能力的人。
资源服务器:资源所在的服务器。
授权服务器:为客户端应用程序提供不同的 Token,可以和资源服务器在统一服务器上,也可以独立出去。
授权码模式(authorization code)
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与 "服务提供商" 的认证服务器进行互动。流程如下:
用户访问客户端,后者将前者导向认证服务器。
用户选择是否给予客户端授权。
假设用户给予授权,认证服务器将用户导向客户端事先指定的 "重定向 URI"(redirection URI),同时附上一个授权码。
客户端收到授权码,附上早先的 "重定向 URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
简化模式(implicit)
简化模式(Implicit Grant Type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了 "授权码" 这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。流程如下:
客户端将用户导向认证服务器。
用户决定是否给于客户端授权。
假设用户给予授权,认证服务器将用户导向客户端指定的 "重定向 URI",并在 URI 的 Hash 部分包含了访问令牌。
浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值。
资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌。
浏览器执行上一步获得的脚本,提取出令牌。
浏览器将令牌发给客户端。
密码模式(Resource Owner Password Credentials)
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 "服务商提供商" 索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。流程如下:
用户向客户端提供用户名和密码。
客户端将用户名和密码发给认证服务器,向后者请求令牌。
认证服务器确认无误后,向客户端提供访问令牌。
客户端模式(Client Credentials)
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向 "服务提供商" 进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。
在这种模式中,用户直接向客户端注册,客户端以自己的名义要求 "服务提供商" 提供服务,其实不存在授权问题。流程如下:
客户端向认证服务器进行身份认证,并要求一个访问令牌。
认证服务器确认无误后,向客户端提供访问令牌。
五、思考总结
正如 David Borsos 所建议的一种方案,在微服务架构下,我们更倾向于将 Oauth 和 JWT 结合使用,Oauth 一般用于第三方接入的场景,管理对外的权限,所以比较适合和 API 网关结合,针对于外部的访问进行鉴权(当然,底层 Token 标准采用 JWT 也是可以的)。
JWT 更加轻巧,在微服务之间进行访问鉴权已然足够,并且可以避免在流转过程中和身份认证服务打交道。当然,从能力实现角度来说,类似于分布式 Session 在很多场景下也是完全能满足需求,具体怎么去选择鉴权方案,还是要结合实际的需求来。
粉丝福利:Java从入门到入土学习路线图
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈