认证与授权(一):OAuth和JWT
1、基础应用
应用的安全主要关注两个方面:认证(Authentication)和授权(Authoriztion),即你是谁,以及你能做什么。在单体应用中,开发者可以通过简单的拦截器以及会话(Session)机制对用户的访问进行控制和记录。而在分布式系统中,由于业务逻辑封装在各个微服务中,每个微服务都需要对用户的行为进行认证和许可,于是就产生了两种可能的方式:
第一种是通过一个中心化的权限管理系统,对用户的身份和权限进行统一的管理,可以做到一次授权,多次多点使用,但是这个独立的安全微服务需要聚合各个微服务中的权限控制逻辑,当添加一个新的基于不同业务逻辑实现的微服务就可能需要在安全微服务中添加新的实现。
第二种是将安全控制分散到各个微服务中,由各个微服务根据自身的业务对用户的访问进行管理和控制。这会导致安全管理过于分散,甚至每个微服务都有自己的一套实现方式,不利于统一管理。
这两种方式各有利弊,如何选择需要根据项目的具体业务需求进行判断,甚至在一定情况下可以结合使用。
Spring Cloud Security提供了一组基本的组件用来构建安全应用程序和服务。它封装了Spring Securtiy、Spring Security OAuth2和Spring Security JWT的相关实现,同时提供自带的安全特性,致力于为Spring Cloud微服务提供快速创建常用安全模式的能力。
2、OAuth2简介
OAuth协议的目的是为用户资源的授权提供一个安全的、开放而简易的标准。
2.1、角色
OAuth2中主要分为了4种角色:
Resource Owner(资源所有者),是能够对受保护的资源授予访问权限的实体,可以是一个用户,这时会称为终端用户(end-user)。
Resource Server(资源服务器),持有受保护的资源,允许持有访问令牌(Access Token)的请求访问受保护资源。
Client(客户端),持有资源所有者的授权,代表资源所有者对受保护资源进行访问。
Authorization Server(授权服务器),对资源所有者的授权进行认证,成功后向客户端发送访问令牌。
2.2、协议流程
OAuth2的认证流程如图所示:
Oauth认证流程.jpg
- 客户端(浏览器)请求资源所有者(用户)的授权。
- 资源所有者(用户)同意授权,返回授权许可(Authorization Grant),这代表了资源所有者的授权凭证。
- 客户端(浏览器)携带授权许可要求授权服务器进行认证,请求访问令牌。
- 授权服务器对客户端(浏览器)进行身份验证,并认证授权许可,如果有效,返回访问令牌。
- 客户端(浏览器)携带访问令牌向资源服务器请求受保护资源的访问。
- 资源服务器验证访问令牌,如果有效,接受访问请求,返回受保护资源。
认证流程可类比微信开放平台接入微信登录功能。
微信登录.png
- 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
- 通过code参数加上AppID和AppSecret等,通过API换取access_token;
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
3、JWT
JWT(JSON Web Token)作为一个开放的标准,通过紧凑(Compact)或者自包含(Self-contained)的方式,定义了用于在各方之间发送的安全JSON对象。
JWT可以很好地充当在上面介绍的访问令牌和刷新令牌的载体,这是Web双方之间进行安全传输信息的良好方式。只有授权服务器持有签发和验证JWT的密钥,那么就只有授权服务器能验证JWT的有效性以及发送带有签名的JWT,这就唯一保证了以JWT为载体的令牌的有效性和安全性。
JWT的一般格式如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0
.IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
它由三部分组成,每部分通过.分隔开,分别是:
Header(头部)
Payload(有效负荷)
Signature(签名)
3.1、头部
头部通常由两部分组成:
·typ:类型,一般为JWT。
·alg:加密算法,通常是HMAC SHA256或者RSA。
一个简单的头部例子如下所示:
{
"alg": "HS256"
"typ": "JWT"
}
这部分JSON会由Base64Url编码,构成JWT的第一部分,如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
3.2、有效载荷
有效负荷(Payload)是JWT的第二部分,是用来携带有效信息的载体,主要是关于用户实体和附加元数据的声明,组成如下:
- Registered claims(注册声明)。这是一组预定的声明,但并不强制要求。它提供了一套有用的、能共同使用的声明。主要有iss(JWT签发者)、exp(JWT过期时间)、sub(JWT面向的用户)、aud(接受JWT的一方)等。
- Public claims(公开声明)。公开声明中可以添加任何信息,一般是用户信息或者业务扩展信息等。
- Private claims(私有声明)。由JWT提供者和消费者共同定义的声明,既不属于注册声明也不属于公开声明。
一般不建议在Payload中添加任何敏感信息,因为Base64是对称解密的,这意味着Payload中的信息是可见的。
一个简单的Payload例子如下所示:
{
"name": "cang wu",
"exp": 1518051157,
"userId": "123456"
}
这部分JSON会由Base64Url编码,构成JWT的第二部分,如下所示:
eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0
3.3、签名
要创建签名,需要编码后的头部、编码后的Payload、一个密钥,最后通过在头部alg键值定义的加密算法加密生成签名,生成签名的伪代码如下所示:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
上述代码中用到的加密算法为HMACSHA256。密钥保存在服务端用于验证JWT以及签发JWT,所以必须只由服务端持有,不该泄露出去。
一个简单的签名如下所示:
IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
这将成为JWT的第三部分。
这三部分通过“.”分割,组成最终的JWT,如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0
.IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs