JWT(JSON Web Token)是一个非常轻巧的规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,通常使用在HTTP通信过程中进行身份认证。

我们知道,HTTP通信是无状态的,客户端的请求到了服务器处理完之后是无法返回给原来的客户端的,因此需要对访问的客户端进行识别,常用的做法是通过Session机制:客户端在服务端登录成功后,服务端会生成一个sessionId返回给客户端,然后客户端会把这个sessionId存到本地浏览器的Cookie中,当客户端再向服务端发起请求就会从Cookie中取出这个sessionId并附加到请求头中。这样服务端就能够知道是哪个用户发起的请求。

Session存在的问题

1.Session是保存在服务端的,当客户访问量增加的时候,服务端就需要存储大量的Session会话,对响应性能造成影响。

2.当服务器为集群的时候,用户登录其中的一台服务器,会将Session保存到该服务器的内存中,但是当用户访问到其他服务器的时候,因为在该服务器的内存中找不到该Session,就会重新生成一个Session返回给客户端,造成分布式Session共享问题。通常会采用缓存一致性的技术来解决分布式Session的共享问题,或者是使用第三方缓存(比如Redis)来保存Session,其实也可以使用JWT来解决这个问题,也就是使用JWT替代Session。

JWT的产生与使用步骤

1.客户端通过用户名和密码登录服务器。

2.服务端对客户端进行身份验证。

3.服务端对该用户生成Token,返回给客户端。

4.客户端发起请求,需要携带该Token。

5.服务端收到请求后,首先验证Token,然后再返回数据。

6.客户端将Token保存到本地浏览器,一般也是保存到Cookie中。

这样,服务端就不需要保存Token,只需要对Token中携带的信息进行验证即可。

无论客户端访问后台的哪台服务器,只要可以通过用户信息的验证即可。

JWT的原理

JWT的原理是,服务器认证之后,生成一个JSON对象,返回给用户,就像下面这样。

{
"姓名": "张飞",
"角色": "管理员",
"到期时间": "2019年05月20日0时0分"
}

以后用户和服务器进行通信的时候都要发送这个JSON对象。服务器完全只靠这个对象认证用户信息。为了防止用户篡改数据,服务器在生成这个对象的时候,都会加上签名。这样,服务器就不需要保存任何Session会话信息了,也就是说,服务器变成无状态的了,从而比较容易实现扩展。

JWT的数据结构

实际的JWT大概就是像下面这个样子:

这是一个很长的字符串,中间用点分隔符【.】分隔成三个部分(要注意,JWT内部是没有换行的)。

JWT由三个部分组成:头部(Header)、负载(Payload)和签名(Signature)。

Header

Heade部分是一个JSON对象,是描述JWT的元数据。通常是下面这种格式的:

{
"alg": "HS256",
"typ": "JWT"
}

在上面的代码中,alg属性表示签名的算法(Algorithm),默认是HMAC SHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT令牌固定写为JWT。最后,将上面的JSON对象使用Base64URL算法转成字符串。

Payload

Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段供选用。

iss(issuer) 签发人
exp(expiration time) 过期时间
sub(subject) 主题
aud(audience) 受众
nbf(not before) 生效时间
iat(issued at) 签发时间
jti(jwt id) 编号

除了这些官方字段,也可以在这个部分定义私有字段:

{
"sub": "buybuybuy",
"name": "yanggb",
"admin": true
}

注意这个JWT部分默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

这个JSON对象也要使用Base64URL算法转成字符串。

Signature

Signature部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。

然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

最后,在算出签名之后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间用点分隔符【.】分割,就可以返回给用户了。

Base64URL算法

前面提到,Header和Payload串型化的算法是Base64URL。这个算法跟Base64算法基本类似,但有一些小的不同。

JWT作为一个令牌(Token),有些场合可能会放到URL(比如api.yanggb.com/?token=xxx)。Base64有三个字符【+】、【/】和【=】,在URL里面是有特殊含义的,所以需要被替换掉:【=】号被省略、【+】号被替换成【-】、【/】被替换成【_】。这就是Base64URL算法和Base64算法的不同。

JWT的使用方式

客户端收到服务端返回的JWT,可以存储在Cookie里面,也可以存储在localStorage里面。此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样就不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization字段里面。

Authorization: Bearer <token>

另一种做法是在跨域的时候将JWT放在POST请求的数据体里面。

JWT的几个特点

1.JWT默认是不加密,但是也是可以加密的。生成原始的Token以后,可以用密钥再加密一次。JWT不加密的情况下,不能将秘密数据写入JWT。

2.JWT不仅可以用于认证,也可以用于交换信息。有效地使用JWT,可以降低服务器查询数据库的次数。

3.JWT的最大缺点是由于服务器不保存Session会话状态,因此无法在使用过程中废止某个Token或者更改Token的权限。意思就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器有部署额外的处理逻辑。

4.JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

5.为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输。

JWT的使用实例(Demo)

在Maven项目中添加JWT的依赖、相关依赖和插件。

<!-- JWT -->
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

创建一个单元测试类。

public class JWTDemo {
// 加密的KEY
private static final String SECRET_KEY = "123456"; @Test
public void jwtTest() throws InterruptedException {
// 创建JWT
long time = System.currentTimeMillis() + 30*60*1000;
String jwt = this.buildJwt(new Date(time));
System.out.println("JWT字符串:" + jwt); // 验证token是否可用
boolean vaild = this.isJwtValid(jwt);
System.out.println("Token可用:" + vaild);
} /**
* 创建JWT
* @param exp 过期时间
* @return JWT String
*/
private String buildJwt(Date exp) {
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // SECRET_KEY是加密算法对应的密钥,这里使用额是HS256加密算法
.claim("name", "yanggb")
.setExpiration(exp) // expTime是过期时间
.compact();
} private boolean isJwtValid(String jwt) {
try {
// 解析JWT字符串中的数据,并进行最基础的验证
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY) // SECRET_KEY是加密算法对应的密钥,jjwt可以自动判断机密算法
.parseClaimsJws(jwt) // jwt是JWT字符串
.getBody();
System.out.println(claims);
}
// 在解析JWT字符串时,如果密钥不正确,将会解析失败,抛出SignatureException异常,说明该JWT字符串是伪造的
// 在解析JWT字符串时,如果过期时间字段已经早于当前时间,将会抛出ExpiredJwtException异常,说明本次请求已经失效
catch (SignatureException | ExpiredJwtException e) {
return false;
}
return true;
}
}

运行之后就可以在控制台看到打印出来的信息,然后可以使用JWT的解析工具解析出内容(https://jwt.io/):

这些就是JWT的入门知识。

"我以为回忆终究会慢慢搁浅,化作泡沫,消失在时光の海的边陲。可是记忆的种子却时而开花,时而凋零,告诉我们:今后无论去哪,都要勇敢。"

最新文章

  1. 如何从源码中学习javascript
  2. MR21、MR22和CK24的区别
  3. 【转载】Android 自动化测试 Emmagee
  4. 使用HttpClient 发送get、post请求,及其解析xml返回数据
  5. 破解Excel密码保护文件
  6. 动态导入(import)和静态导入(import)的区别
  7. 在caffe中使用hdf5的数据
  8. gluster 安装配置基本指南
  9. mysql学习笔记6——用phpmyadmin和在腾讯微云中创建数据库
  10. linux 多线程基础2
  11. 结合rpyc使用python实现动态升级的方法
  12. 老司机带你用vagrant打造一站式python开发测试环境
  13. mongodb更新数据
  14. 《剑指offer》— JavaScript(19)顺时针打印矩阵
  15. Java并发之CountDownLatch、CyclicBarrier和Semaphore
  16. 2017广东工业大学程序设计竞赛决赛 题解&amp;源码(A,数学解方程,B,贪心博弈,C,递归,D,水,E,贪心,面试题,F,贪心,枚举,LCA,G,dp,记忆化搜索,H,思维题)
  17. 1013团队Beta冲刺day2
  18. PHP整洁之道
  19. C++学习笔记44:继承与派生
  20. R链接hive/oracle/mysql

热门文章

  1. Python中model转dict
  2. WCF全双工通信实例分享(wsDualHttpBinding、netTcpBinding两种实现方式)
  3. NetCore 下使用 DataTable 以及可视化工具
  4. 史上最全Winform中使用ZedGraph教程与资源汇总整理(附资源下载)
  5. System 类初探
  6. sqlserver2008R2 本地不能用localhost连接
  7. SQL学习_SQL函数
  8. git在idea中的冲突解决(非常重要)
  9. Shell命令-用户用户组管理之passwd、chage
  10. 设置了相对定位relative之后,改变top值,如何去掉多余空白?