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

一、JWT是什么

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

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

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的前面两部分进行加密后的字符串,将HeadersPlayload进行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的实现方式来完成JWTrefresh_token

思路大概就是用户登录成功后,签发token的同时,生成一个加密串作为refresh_tokenrefresh_token存放在redis中,设置合理的过期时间(一般会将refresh_token的过期时间设置的比较久一点)。然后将tokenrefresh_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作为一个轻量级的鉴权框架,使用起来非常方便,但是也会存在一些问题,

  • JWTPlayload部分只是经过base64编码,这样我们的信息其实就完全暴露了,一般不要将敏感信息存放在JWT中。

  • JWT生成的token比较长,每次在请求头中携带token,导致请求偷会比较大,有一定的性能问题。

  • JWT生成后,服务端无法废弃,只能等待JWT主动过期。

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

  • 有效期短

  • 只希望被使用一次

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

使用jwt来保护你的接口服务的更多相关文章

  1. 使用JWT设计SpringBoot项目api接口安全服务

    转载直: 使用JWT设计SpringBoot项目api接口安全服务

  2. 基于ASP.NET Core 3.x的端点路由(Endpoint Routing)实现控制器(Controller)和操作(Action)分离的接口服务

    本文首发于 码友网 -- <基于ASP.NET Core 3.x的端点路由(Endpoint Routing)实现控制器(Controller)和操作(Action)分离的接口服务> 前言 ...

  3. day114:MoFang:基于支付宝沙箱测试环境完成创建充值订单接口&服务端处理支付结果的同步通知和异步通知

    目录 1.基于支付宝提供的沙箱测试环境开发支付接口 1.后端提供创建充值订单接口 2.前端调用AlipayPlus发起支付 3.注意:自定义APPLoader完成接下来的开发 4.下载支付宝沙箱钱包A ...

  4. Thrift对多接口服务的支持

    Thrift对多接口服务的支持 Thrift在0.9.1版本之前,一直只提交了对单一接口服务的支持,即一个RPC服务器(对应一个端口)支持一个服务接口的实现. 但是很多时候,我们的服务不能实现在一个接 ...

  5. SpringBoot接口服务处理Whitelabel Error Page(转)

    To switch it off you can set server.error.whitelabel.enabled=false http://stackoverflow.com/question ...

  6. API接口服务端

    <?php /** * API接口服务端 * * */ require 'mysql_class.php'; header('Content-Type:text/html;charset=utf ...

  7. dotnet core 开发无缝兼容Http和Websocket协议的接口服务

    在应用接口开发中往往要针对不同协义开发相应的代理服务,但对于Websocket和http这两种协议来说就有些不同,从实现上来看Websocket可以说是Http的升级子协议, 两者在协议处理上基本一致 ...

  8. ASP.NET WebAPI构建API接口服务实战演练

    一.课程介绍 一.王小二和他领导的第一次故事 有一天王小二和往常一下去上早班,刚吃完早餐刚一打开电脑没一会儿.王小二的领导宋大宝走到他的面前,我们现在的系统需要提供服务给其他内部业务系统,我看你平时喜 ...

  9. Derek解读Bytom源码-Api Server接口服务

    作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...

随机推荐

  1. NUC980 运行 RT-Thread 时使用 GPIO

    如何使用 GPIO? NuMaker-RTU-NUC980 板子引出的 IO 有: 分别有一个 I2C1.GPIO.SPI0.UART4,RT-Thread 中 NuMaker-RTU-NUC980 ...

  2. base64的实现原理

    base64是处理二进制数据的一种编码方式,可用于把二进制数据编码成64个可打印的字符. 学习base64之前,先了解一下什么是字节与编码 什么是字节 互联网中的数据都是用字节来表示的,一个字节有8位 ...

  3. SuperEdge 云边隧道新特性:从云端SSH运维边缘节点

    背景 在边缘集群的场景下边缘节点分布在不同的区域,且边缘节点和云端之间是单向网络,边缘节点可以访问云端节点,云端节点无法直接访问边缘节点,给边缘节点的运维带来很大不便,如果可以从云端SSH登录到边缘节 ...

  4. 附加数据库出现 无法打开物理文件 操作系统错误 5:拒绝访问 SQL

    刚刚从公司的电脑上考到自己刚刚装好系统的笔记本上面,出现了问题: 无法打开物理文件 操作系统错误 5:拒绝访问 . 网上找了下解决方法: 找到需要导入的  mdf和ldf  修改它的权限为完全控制,不 ...

  5. POJ 1572 Automatic Editing 字符串替换,replace就够了

    题意不难理解,但是一开始还是没有看清楚题目.Replace the first occurrence of the find string within the text by the replace ...

  6. ubuntu 更换apache网站根目录/var/www/html及端口

    1)修改/etc/apache2/ports.conf 80是默认监听端口,所以可以新增一个监听端口8010 2)在/etc/apache2/sites-available目录新增配置文件auto-t ...

  7. Python之面向对象编程【小明跑步】、【置办家具】

    #!usr/bin/python 2 #encoding=utf-8 3 #-----------------小明跑步------------- 4 #1.小明体重75.0公斤 5 #2.小明每次跑步 ...

  8. 官宣.NET 6 预览版4

    我们很高兴发布 .NET 6 Preview 4.我们现在大约完成了 .NET 6 发布的一半.现在是一个很好的时机,可以再次查看.NET6的完整范围.许多功能都接近最终形式,而其他功能将很快推出,因 ...

  9. ROS2学习之旅(4)——理解ROS2 Graph中的节点

    ROS(2)图(ROS(2) graph)是一个同时处理数据的基于ROS2元素的网络,它包含了所有的可执行文件以及它们之间的连接.图中的基本元素包括:节点(nodes).话题(topics).服务(s ...

  10. rz上传文件报错:rpm Read Signature failed: sigh blob(1268): BAD, read returned 0

    上传文件报错: [root@www localdisk]# rpm -ivh cobbler* error: cobbler-2.8.4-4.el7.x86_64.rpm: rpm  Read  Si ...