如果对cookie/token有疑问的,可以查看之前的博客快速了解会话管理三剑客cookie、session和JWT
Json Web Token (JWT)是为在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT被设计为紧凑且安全,特别适用于分布式站点的单点登录(SSO)场景。JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息。
本文将针对JWT在身份验证业务场景下的应用进行讲解。
JWT的表现形式是个字符串,它由头部、载荷与签名这三部分组成,中间以「.」分隔。像下面这样:
头部帮助应用程序定义如何处理接收到的令牌。头部信息以JSON格式显示,转化为JWT时需要用base64url算法进行编码。
{
"alg": "HS256",
"typ": "JWT"
}
typ:令牌类型 alg:用于生成签名的算法
载荷用来存储传递的数据,比如用户信息的姓名、性别、年龄等。载荷信息以JSON格式显示,转化为JWT时需要用base64url算法进行编码。要注意的是机密信息不要放到这里,比如密码等。
{
"name": "全菜工程师小辉",
"introduce": "啥都不会"
}
JWT规定了7个默认字段供开发者选用。
iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众,相当于接受者 nbf (Not Before):生效的起始时间 iat (Issued At):签发时间 jti (JWT ID):编号,唯一标识
对于每种加密算法,签名都对应的一个计算公式。例如SHA256加密算法的签名如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload) + "." +
Secret
)
当网关或者服务收到JWT时会计算签名的值,并将其与接收到的签名进行对比。如果不相同,则意味着该令牌已被不可信的一方修改或生成。
Secret(秘钥)是一定不可以不能泄露。对于非对称加密和对称加密,秘钥的形式是不同的,安全性也不一样,但并不一定对称加密就不好。有关这个问题的讨论,之后的博客再详细讲解。
注:验证JWT可以使用参考文档2的网站。
对称加密是最快速、最简单的一种加密方式,加密与解密用的是同样的密钥。
非对称加密可以在不直接传递密钥的情况下完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。公钥和私钥是成对的,可以互相解密。
非对称加密中:
公钥加密,私钥解密:可以实现消息加密,防止信息被泄露。这样只有持有对应私钥的服务才能将消息明文解析。
私钥加密,公钥解密:可以实现数字签名,防止信息被篡改。这样可以确实是谁发来的消息。因为服务端的公钥只能解对应方的私钥加密的签名信息。(签名信息可以是摘要未加密信息中的一部分信息,例如JWT中的签名)
对称加密中,加解密使用同一个密钥,如果秘钥泄露,会发生极大的危险且很难察觉。
对称加密中,签名和验签使用同一个密钥,也就意味着验签者既可以验签,也能对数据进行重新签名、伪造签名,不能解决造假问题。而非对称算法很好地解决这个问题,签名和验签使用不同的密钥,避免造假问题发生。
单JWT的会话管理流程如下:
对称加密的秘钥为了安全,只放在授权中心,从而导致下游微服务鉴权必须要重复请求授权中心。
一种可行的解决方法是在授权中心首次鉴权通过后,将验证通过的信息存放到header中进行路由传递。但这种解决方法会受到架构和部门协作的影响,不推荐大项目这样做。
另一种可行的解决方法是将授权中心的鉴权功能做成工具包,开放给所有服务引入使用。但这种解决方法会存在秘钥更迭或者泄露的问题,需要基于现有架构进行优化。
私钥仅保存在授权中心,减少秘钥泄露的可能;下游服务可以使用公钥获取JWT信息,不需要频繁与授权中心进行通信,提高了系统的运作效率。
注:实测在Amazon上4c8g的云服务上,从token模式转换成JWT模式,注册qps提升4倍且未遇到性能瓶颈。
为了用户体验,accesstoken会设置较长时间,但是JWT形式的accesstoken包含了与用户相关的验证消息,通常情况下是不会被服务端保存,这就导致一个严重的问题当客户端重置密码后或用户被封禁的时候,无法阻拦用户的请求。
JWT登录鉴权增加refreshtoken机制(双JWT机制)来解决这个问题。
refreshtoken是OAuth2认证中的一个概念,一般称为“更新令牌”,和OAuth2的accesstoken同时生成。作用是用来获取新的accesstoken,不用于接口请求的身份认证。
通常情况下,refreshtoken的有效期会比较长,而accesstoken的有效期比较短。当accesstoken由于过期而失效时,使用refreshtoken就可以获取到新的accesstoken,如果refreshtoken失效了,用户就只能重新登录(但在某些业务场景,业务方想要自动续期。下一节针对这个问题有思考)。
引入refreshtoken后,会话管理流程改进如下:
refreshtoken获取流程:
refreshtoken使用流程:
在用户登录时,将生成的refreshtoken和用户信息进行保存。当用户被封禁时,直接将用户信息或者对应的refreshtoken加入黑名单。
黑名单在刷新接口的时候进行校验,从而实现了双JWT场景下的权限管理。
有人可能会觉得加在网关层会更好。但如果黑名单加在网关层的话,就失去了JWT使用的初衷,将JWT模式变成了token模式,所以不提倡在网关层加黑名单。
由于客户端无法获取到新的accesstoken,从而再也无法访问需要认证的接口。这样的方式虽然会有一定的窗口期(取决于accesstoken的失效时间),但基本上可以适应常规情况下对用户登录鉴权的精度要求。
在某些业务场景,业务方想要用户鉴权自动续期(即用户长期不需要手动登录或者永久不需要手动登录直到手动取消授权)。
这里给出可行的方案,但实际上都是有需要规避的安全风险。
参考文档2的网站列出了各种语言对应的JWT库。
由于Auth0提供的JWT库简单实用,小辉项目中使用Auth0实现JWT功能。
Auth0的代码见参考文档1。
引入Auth0只需要在pom.xml文件中增加如下代码:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.9.0</version>
</dependency>
以下为小辉项目中,脱敏后的简化代码,读者可以参考下。
private static String jwtSecret = "s222dad@@13fhu123129=1232!!!3PPPdsadsashdhbn@@!!sdauS";
private static String jwtIssuer = "xiaohui";
/**
* 对称加密算法,HMAC256创建JWT
*/
public static String creatJWT(){
String token = null;
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
token = JWT.create()
.withIssuedAt(new Date())
.withExpiresAt(DateUtils.addHours(new Date(), 2))
.withIssuer(jwtIssuer)
.withNotBefore(new Date())
.sign(algorithm);
} catch (JWTCreationException e) {
log.error("creatJWT error {}", e);
}
return token;
}
/**
* 检验JWT
* @param token
* @return
*/
public static boolean checkJWT(String token) {
DecodedJWT decodedJWT = null;
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(jwtIssuer)
.build();
decodedJWT = verifier.verify(token);
} catch (TokenExpiredException e) {
log.info("checkJWT timeout,token is {},for more information {}", token, e);
return false;
} catch (JWTVerificationException e) {
log.info("checkJWT error,token is {},for more information {}", token, e);
return false;
}
return true;
}
项目也有非对称加密算法RSA256的解决方案,不过综合考虑项目架构、工期与安全性等因素,最后小辉在生产项目使用的是HS256的对称加密算法。
JWT需要添加一些与业务相关的参数用于检验,可以有效提高接口被爬的门槛和提高服务的安全性。更多有关JWT安全问题,之后的博客再详细讲解。
参考文档: