共计 4351 个字符,预计需要花费 11 分钟才能阅读完成。
以前写过一篇对于接口服务标准的文章,原文在此,外面对于安全性问题重点讲述了通过 appid
,appkey
,timestamp
,nonce
以及 sign
来获取 token
,应用token
来保障接口服务的平安。明天咱们来讲述一种更加便捷的形式,应用 jwt
来生成 token。
一、JWT 是什么
JSON Web Token
(JWT
)定义了一种紧凑且自蕴含的形式,用于在各方之间作为 JSON 对象平安地传输信息。该信息能够被验证和信赖,因为它是通过数字签名的。JWT
能够设置有效期。
JWT
是一个很长的字符串,蕴含了 Header
,Playload
和Signature
三局部内容,两头用 .
进行分隔。
Headers
Headers
局部形容的是 JWT
的根本信息,个别会蕴含签名算法和令牌类型,数据如下:
{
"alg": "RS256",
"typ": "JWT"
}
Playload
Playload
就是寄存无效信息的中央,JWT
规定了以下 7 个字段,倡议但不强制应用:
iss: jwt 签发者
sub: jwt 所面向的用户
aud: 接管 jwt 的一方
exp: jwt 的过期工夫,这个过期工夫必须要大于签发工夫
nbf: 定义在什么工夫之前,该 jwt 都是不可用的
iat: jwt 的签发工夫
jti: jwt 的惟一身份标识,次要用来作为一次性 token
除此之外,咱们还能够自定义内容
{
"name":"Java 旅途",
"age":18
}
Signature
Signature
是将 JWT
的后面两局部进行加密后的字符串,将 Headers
和Playload
进行 base64
编码后应用 Headers
中规定的加密算法和密钥进行加密,失去 JWT
的第三局部。
二、JWT 生成和解析 token
在应用服务中引入 JWT
的依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
依据 JWT
的定义生成一个应用 RSA
算法加密的,有效期为 30 分钟
的 token
public static String createToken(User user) throws Exception{return Jwts.builder()
.claim("name",user.getName())
.claim("age",user.getAge())
// rsa 加密
.signWith(SignatureAlgorithm.RS256, RsaUtil.getPrivateKey(PRIVATE_KEY))
// 有效期 30 分钟
.setExpiration(DateTime.now().plusSeconds(30 * 60).toDate())
.compact();}
登录接口验证通过后,调用 JWT
生成带有用户标识的 token 响应给用户,在接下来的申请中,头部携带 token
进行验签,验签通过后,失常拜访应用服务。
public static Claims parseToken(String token) throws Exception{
return Jwts
.parser()
.setSigningKey(RsaUtil.getPublicKey(PUBLIC_KEY))
.parseClaimsJws(token)
.getBody();}
三、token 续签问题
下面讲述了对于 JWT
验证的过程,当初咱们思考这样一个问题,客户端携带 token
拜访下单接口,token
验签通过,客户端下单胜利,返回下单后果,而后客户端带着 token
调用领取接口进行领取,验签的时候发现 token 生效了,这时候应该怎么办?只能通知用户 token
生效,而后让用户从新登录获取 token
?这种体验是十分不好的,oauth2
在这方面做的比拟好,除了签发 token
,还会签发refresh_token
,当token
过期后,会去调用 refresh_token
从新获取 token
,如果refresh_token
也过期了,那么再提醒用户去登录。当初咱们模仿 oauth2
的实现形式来实现 JWT
的refresh_token
。
思路大略就是用户登录胜利后,签发 token
的同时,生成一个加密串作为 refresh_token
,refresh_token
寄存在 redis
中,设置正当的过期工夫(个别会将 refresh_token
的过期工夫设置的比拟久一点)。而后将 token
和refresh_token
响应给客户端。伪代码如下:
@PostMapping("getToken")
public ResultBean getToken(@RequestBody LoingUser user){ResultBean resultBean = new ResultBean();
// 用户信息校验失败,响应谬误
if(!user){resultBean.fillCode(401,"账户明码不正确");
return resultBean;
}
String token = null;
String refresh_token = null;
try {
// jwt 生成的 token
token = JwtUtil.createToken(user);
// 刷新 token
refresh_token = Md5Utils.hash(System.currentTimeMillis()+"");
// refresh_token 过期工夫为 24 小时
redisUtils.set("refresh_token:"+refresh_token,token,30*24*60*60);
} catch (Exception e) {e.printStackTrace();
}
Map<String,Object> map = new HashMap<>();
map.put("access_token",token);
map.put("refresh_token",refresh_token);
map.put("expires_in",2*60*60);
resultBean.fillInfo(map);
return resultBean;
}
客户端调用接口时,在申请头中携带 token
,在拦截器中拦挡申请,验证token
的有效性,如果验证 token
失败,则去 redis 中判断是否是 refresh_token
的申请,如果 refresh_token
验证也失败,则给客户端响应鉴权异样,提醒客户端从新登录,伪代码如下:
HttpHeaders headers = request.getHeaders();
// 申请头中获取令牌
String token = headers.getFirst("Authorization");
// 判断申请头中是否有令牌
if (StringUtils.isEmpty(token)) {resultBean.fillCode(401,"鉴权失败,请携带无效 token");
return resultBean;
}
if(!token.contains("Bearer")){resultBean.fillCode(401,"鉴权失败,请携带无效 token");
return resultBean;
}
token = token.replace("Bearer","");
// 如果申请头中有令牌则解析令牌
try {Claims claims = TokenUtil.parseToken(token).getBody();} catch (Exception e) {e.printStackTrace();
String refreshToken = redisUtils.get("refresh_token:" + token)+"";
if(StringUtils.isBlank(refreshToken) || "null".equals(refreshToken)){resultBean.fillCode(403,"refresh_token 已过期,请从新获取 token");
return resultbean;
}
}
refresh_token
来换取 token
的伪代码如下:
@PostMapping("refreshToken")
public Result refreshToken(String token){ResultBean resultBean = new ResultBean();
String refreshToken = redisUtils.get(TokenConstants.REFRESHTOKEN + token)+"";
String access_token = null;
try {Claims claims = JwtUtil.parseToken(refreshToken);
String username = claims.get("username")+"";
String password = claims.get("password")+"";
LoginUser loginUser = new LoginUser();
loginUser.setUsername(username);
loginUser.setPassword(password);
access_token = JwtUtil.createToken(loginUser);
} catch (Exception e) {e.printStackTrace();
}
Map<String,Object> map = new HashMap<>();
map.put("access_token",access_token);
map.put("refresh_token",token);
map.put("expires_in",30*60);
resultBean.fillInfo(map);
return resultBean;
}
通过下面的剖析,咱们简略的实现了 token
的签发,验签以及续签问题,JWT
作为一个轻量级的鉴权框架,应用起来十分不便,然而也会存在一些问题,
JWT
的Playload
局部只是通过 base64 编码,这样咱们的信息其实就齐全裸露了,个别不要将敏感信息寄存在JWT
中。JWT
生成的token
比拟长,每次在申请头中携带token
,导致申请偷会比拟大,有肯定的性能问题。JWT
生成后,服务端无奈废除,只能期待JWT
被动过期。
上面这段是我网上看到的一段对于 JWT
比拟实用的场景:
- 有效期短
- 只心愿被应用一次
比方,用户注册后发一封邮件让其激活账户,通常邮件中须要有一个链接,这个链接须要具备以下的个性:可能标识用户,该链接具备时效性(通常只容许几小时之内激活),不能被篡改以激活其余可能的账户,一次性的。这种场景就适宜应用JWT
。