JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用户登录。在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保存一个session,服务端会返回给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。 cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题。虽然目前存在使用Redis进行Session共享的机制,但是随着用户量和访问量的增加,Redis中保存的数据会越来越多,开销就会越来越大,多服务间的耦合性也会越来越大,Redis中的数据也很难进行管理,例如当Redis集群服务器出现Down机的情况下,整个业务系统随之将变为不可用的状态。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)
完整的头部就像下面这样的JSON:
{
"typ": "JWT",
"alg": "HS256"
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
"uid":"e12a34b56c78d9e0f",
"name":"ramostear",
"role":"admin"
}
然后将其进行base64加密,得到Jwt的第二部分:
eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9
49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
下图为一个JWT生成流程示例:
在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token 返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。以访问 API 资源为例,下图显示了获取并使用 JWT 的基本流程:
添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
生成以及解析 jwt token方法:
/** * 生成token方法 * @param claims map对象,可传递需要携带的参数 * @return */
public String generateToken(Map<String, Object> claims) {
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.forName(alg), secret).compact();
}
/** * 获得token内的内容 * @param token * @return */
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = new DefaultClaims();
log.warn("{}", e.getMessage(), e);
}
return claims;
}
处理 jwt token 过期问题:
/** * 生成过期时间 * @return date */
public Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * MILLISECOND);
}
/** * 判断token是否失效 * @param token * @return */
public Boolean isTokenExpired(String token) {
final Date expirationDate = getExpirationDateFromToken(token);
if (expirationDate == null) {
return false;
}
return expirationDate.before(new Date());
}
/** * 获取过期时间 * @param token * @return date */
public Date getExpirationDateFromToken(String token) {
Date expirationDate;
try {
//获得token内的内容
final Claims claims = getClaimsFromToken(token);
expirationDate = claims.getExpiration();
} catch (Exception e) {
expirationDate = null;
log.warn("{}", e.getMessage(), e);
}
return expirationDate;
}
jwt token定期刷新处理方法:
/** * 刷新token * * @param token jwt的token * @return 刷新后的token */
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
log.warn("{}", e.getMessage(), e);
}
return refreshedToken;
}
1、 使用 JWT 的优势 使用 JSON Web Token 保护应用安全,你至少可以获得以下几个优势:
2、使用 JWT 的弊端
由于JWT这种形式的请求属于无状态的,请求过程中需要等到token过期后采取刷新,在HTTP请求并发这块并没有很好的解决办法;
当服务端在检查到请求的令牌过期之后,会刷新Token重新颁发令牌,并且再次做登录操作,流程上没什么问题,但在页面加载后倘若同一个页面中有多个请求几乎同一时间发起,每一个请求都携带原始令牌,在这样的设计下,就有可能出现在第一个请求到达后刷新了Token,并更改了缓存中的refreshToken的时间戳,以至于剩余请求校验时发现时间戳不一致导致验证失败而在日志中多次打印出当前Token已经失效的log。同时发起的请求越多,log中的异常也就会越多。虽然第一个请求已经刷新了Token,但是其余的请求是失败的,页面中的数据并不完整,显然这是不正常的,那该如何解决呢?
当然实现的方式可以有多种,如我们现在Token过期后刷新再加synchronized生成Token策略,或者前端定时去调用服务端API刷新Token,再如这里即将采用的Token在有效期内定时更新的方式。
在采用有效期内定时刷新的逻辑之前,引用一段介绍:
一个好的模式是在它过期之前刷新令牌。将令牌过期时间设置为一周,并在每次用户打开
Web应用程序并每隔一小时刷新令牌。如果用户超过一周没有打开过应用程序,那他们就
需要再次登录,这是可接受的Web应用程序UX(用户体验)。要刷新令牌,API需要一个新
的端点,它接收一个有效的,没有过期的JWT,并返回与新的到期字段相同的签名的
JWT。然后Web应用程序会将令牌存储在某处。
避免并发情况下token失效问题,可以采用以下方案处理:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/180953.html原文链接:https://javaforall.cn