让JWT来保护你的接口服务

网络 通信技术
以前写过一篇关于接口服务规范的文章,原文在此,里面关于安全性问题重点讲述了通过appid,appkey,timestamp,nonce以及sign来获取token,使用token来保障接口服务的安全。今天我们来讲述一种更加便捷的方式,使用jwt来生成token。

大家好,我是大尧。

以前写过一篇关于接口服务规范的文章,原文在此,里面关于安全性问题重点讲述了通过appid,appkey,timestamp,nonce以及sign来获取token,使用token来保障接口服务的安全。今天我们来讲述一种更加便捷的方式,使用jwt来生成token。

一、JWT是什么

JSON Web Token(JWT) 定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT可以设置有效期。

JWT是一个很长的字符串,包含了Header,Playload和Signature三部分内容,中间用.进行分隔。

Headers

Headers部分描述的是JWT的基本信息,一般会包含签名算法和令牌类型,数据如下:

  1.     "alg""RS256"
  2.     "typ""JWT" 

Playload

Playload就是存放有效信息的地方,JWT规定了以下7个字段,建议但不强制使用:

  1. iss: jwt签发者 
  2. sub: jwt所面向的用户 
  3. aud: 接收jwt的一方 
  4. exp: jwt的过期时间,这个过期时间必须要大于签发时间 
  5. nbf: 定义在什么时间之前,该jwt都是不可用的 
  6. iat: jwt的签发时间 
  7. jti: jwt的唯一身份标识,主要用来作为一次性token 

除此之外,我们还可以自定义内容

  1.     "name":"Java旅途"
  2.     "age":18 

Signature

Signature是将JWT的前面两部分进行加密后的字符串,将Headers和Playload进行base64编码后使用Headers中规定的加密算法和密钥进行加密,得到JWT的第三部分。

二、JWT生成和解析token

在应用服务中引入JWT的依赖

  1. <dependency> 
  2.     <groupId>io.jsonwebtoken</groupId> 
  3.     <artifactId>jjwt</artifactId> 
  4.     <version>0.9.0</version> 
  5. </dependency> 

 

根据JWT的定义生成一个使用RSA算法加密的,有效期为30分钟的token

  1. public static String createToken(User user) throws Exception{ 
  2.  
  3.     return Jwts.builder() 
  4.         .claim("name",user.getName()) 
  5.         .claim("age",user.getAge()) 
  6.         // rsa加密 
  7.         .signWith(SignatureAlgorithm.RS256, RsaUtil.getPrivateKey(PRIVATE_KEY)) 
  8.         // 有效期30分钟 
  9.         .setExpiration(DateTime.now().plusSeconds(30 * 60).toDate()) 
  10.         .compact(); 

登录接口验证通过后,调用JWT生成带有用户标识的token响应给用户,在接下来的请求中,头部携带token进行验签,验签通过后,正常访问应用服务。

  1. public static Claims parseToken(String token) throws Exception{ 
  2.     return Jwts 
  3.         .parser() 
  4.         .setSigningKey(RsaUtil.getPublicKey(PUBLIC_KEY)) 
  5.         .parseClaimsJws(token) 
  6.         .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响应给客户端。伪代码如下:

  1. @PostMapping("getToken"
  2. public ResultBean getToken(@RequestBody LoingUser user){ 
  3.  
  4.     ResultBean resultBean = new ResultBean(); 
  5.     // 用户信息校验失败,响应错误 
  6.     if(!user){ 
  7.         resultBean.fillCode(401,"账户密码不正确"); 
  8.         return resultBean; 
  9.     } 
  10.     String token = null
  11.     String refresh_token = null
  12.     try { 
  13.         // jwt 生成的token 
  14.         token = JwtUtil.createToken(user); 
  15.         // 刷新token 
  16.         refresh_token = Md5Utils.hash(System.currentTimeMillis()+""); 
  17.         // refresh_token过期时间为24小时 
  18.         redisUtils.set("refresh_token:"+refresh_token,token,30*24*60*60); 
  19.     } catch (Exception e) { 
  20.         e.printStackTrace(); 
  21.     } 
  22.  
  23.     Map<String,Object> map = new HashMap<>(); 
  24.     map.put("access_token",token); 
  25.     map.put("refresh_token",refresh_token); 
  26.     map.put("expires_in",2*60*60); 
  27.     resultBean.fillInfo(map); 
  28.     return resultBean; 

客户端调用接口时,在请求头中携带token,在拦截器中拦截请求,验证token的有效性,如果验证token失败,则去redis中判断是否是refresh_token的请求,如果refresh_token验证也失败,则给客户端响应鉴权异常,提示客户端重新登录,伪代码如下:

  1. HttpHeaders headers = request.getHeaders(); 
  2. // 请求头中获取令牌 
  3. String token = headers.getFirst("Authorization"); 
  4. // 判断请求头中是否有令牌 
  5. if (StringUtils.isEmpty(token)) { 
  6.     resultBean.fillCode(401,"鉴权失败,请携带有效token"); 
  7.     return resultBean; 
  8. if(!token.contains("Bearer")){ 
  9.     resultBean.fillCode(401,"鉴权失败,请携带有效token"); 
  10.     return resultBean; 
  11.  
  12. token = token.replace("Bearer ",""); 
  13. // 如果请求头中有令牌则解析令牌 
  14. try { 
  15.     Claims claims = TokenUtil.parseToken(token).getBody(); 
  16. } catch (Exception e) { 
  17.     e.printStackTrace(); 
  18.     String refreshToken = redisUtils.get("refresh_token:" + token)+""
  19.     if(StringUtils.isBlank(refreshToken) || "null".equals(refreshToken)){ 
  20.         resultBean.fillCode(403,"refresh_token已过期,请重新获取token"); 
  21.         return resultbean; 
  22.     } 

refresh_token来换取token的伪代码如下:

  1. @PostMapping("refreshToken"
  2. public Result refreshToken(String token){ 
  3.  
  4.     ResultBean resultBean = new ResultBean(); 
  5.     String refreshToken = redisUtils.get(TokenConstants.REFRESHTOKEN + token)+""
  6.     String access_token = null
  7.     try { 
  8.         Claims claims = JwtUtil.parseToken(refreshToken); 
  9.         String username = claims.get("username")+""
  10.         String password = claims.get("password")+""
  11.         LoginUser loginUser = new LoginUser(); 
  12.         loginUser.setUsername(username); 
  13.         loginUser.setPassword(password); 
  14.         access_token = JwtUtil.createToken(loginUser); 
  15.     } catch (Exception e) { 
  16.         e.printStackTrace(); 
  17.     } 
  18.     Map<String,Object> map = new HashMap<>(); 
  19.     map.put("access_token",access_token); 
  20.     map.put("refresh_token",token); 
  21.     map.put("expires_in",30*60); 
  22.     resultBean.fillInfo(map); 
  23.     return resultBean; 

通过上面的分析,我们简单的实现了token的签发,验签以及续签问题,JWT作为一个轻量级的鉴权框架,使用起来非常方便,但是也会存在一些问题,

  • JWT的Playload部分只是经过base64编码,这样我们的信息其实就完全暴露了,一般不要将敏感信息存放在JWT中。
  • JWT生成的token比较长,每次在请求头中携带token,导致请求偷会比较大,有一定的性能问题。
  • JWT生成后,服务端无法废弃,只能等待JWT主动过期。

下面这段是我网上看到的一段关于JWT比较适用的场景:

  • 有效期短
  • 只希望被使用一次

比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。这种场景就适合使用JWT。

本文转载自微信公众号「Java旅途」,可以通过以下二维码关注。转载本文请联系Java旅途公众号。

 

责任编辑:武晓燕 来源: Java旅途
相关推荐

2021-08-25 23:03:58

区块链数据安全

2010-04-22 15:24:36

邮件安全网络加密服务器

2018-04-08 09:00:00

Let's Encry加密解密

2013-12-10 10:16:39

2020-07-06 11:32:50

HTTPHTTP Header开发者

2011-03-18 13:41:50

2023-12-22 09:03:31

2020-08-18 10:35:18

JWTredis认证

2018-03-02 16:50:43

人工智能机器人

2011-03-07 09:44:09

赤裸的密码密码

2022-03-05 18:25:51

SSLTLS协议

2015-11-19 09:44:34

HTML5定位

2022-08-15 22:28:57

串口访问鸿蒙

2010-08-18 09:07:26

数据泄密防护DLP公司数据

2021-05-26 13:38:45

Google设定密码用户活动

2016-08-15 10:39:36

2022-02-17 08:57:18

内存设计进程

2020-05-14 20:10:12

SSLTLSLinux

2022-10-17 09:15:37

2012-05-11 14:39:07

点赞
收藏

51CTO技术栈公众号