作者 | smileNicky
来源 | cnblogs.com/mzq123/p/13278935.html
1.什么是JWT?
JWT的全称为Json Web Token (JWT),是目前最流行的跨域认证解决方案,是在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),JWT 是一种JSON风格的轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权
引用官方的说法是:
JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以进行验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。
引用官网图片,JWT生成的token格式如图:
2. JWT令牌结构怎么样?
JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:
标头(Header)
有效载荷(Playload)
签名(Signature)
因此,JWT通常如下所示。xxxxx.yyyyy.zzzzz
ok,详细介绍一下这3部分组成
2.1 标头(Header)
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。
* 声明类型,这里是JWT
* 加密算法,自定义
{
"alg": "HS256",
"typ": "JWT"
}
然后进行Base64Url编码得到jwt的第1部分
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2
的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24
个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中 提
供了非常方便的 B BA AS SE E6 64 4E En nc co od de er r和B BA AS SE E6 64 4D De ec co od de er r,用它们可以非常方便的完
成基于 BASE64 的编码和解码
2.2 有效载荷(Playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包
含三个部分:
(1)标准中注册的声明
iss (issuer):表示签发人
exp (expiration time):表示token过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。这些私有的声明其实一般就是指自定义Claim
定义一个payload:
{
"user_id":1,
"user_name":"nicky",
"scope":[
"ROLE_ADMIN"
],
"non_expired":false,
"exp":1594352348,
"iat":1594348748,
"enabled":true,
"non_locked":false
}
对其进行base64加密,得到payload:
eyJ1c2VyX2lkIjoxLCJ1c2VyX25hbWUiOiJuaWNreSIsInNjb3BlIjpbIlJPTEVfQURNSU4iXSwibm9uX2V4cGlyZWQiOmZhbHNlLCJleHAiOjE1OTQzNTIzNDgsImlhdCI6MTU5NDM0ODc0OCwiZW5hYmxlZCI6dHJ1ZSwibm9uX2xvY2tlZCI6ZmFsc2V9
2.3 签名(Signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
签名,是整个数据的认证信息。一般根据前两步的数据,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第3部分
ok,一个jwt令牌的组成就介绍好咯,令牌是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。
下图显示了一个JWT,它已对先前的标头和有效负载进行了编码,并用一个秘密secret进行了签名编码的JWT:
JWT官网提供的在线调试工具:
https://jwt.io/#debugger-io
开源中国提供的base64在线加解密:
https://tool.oschina.net/encrypt?type=3
3. JWT原理简单介绍
引用官网的图,用于显示如何获取JWT,并将其用于访问API或资源:
1、客户端(包括浏览器、APP等)向授权服务器请求授权
2、授权服务器验证通过,授权服务器会向应用程序返回访问令牌
3、该应用程序使用访问令牌来访问受保护的资源(例如API)
4. JWT的应用场景
JWT 使用于比较小型的业务验证,对于比较复杂的可以用OAuth2.0实现
引用官方的说法:
授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单一登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
信息交换:JSON Web令牌是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
5. 与Cookie-Session对比
了解JWT之前先要了解传统的Cookie-Session认证机制,这是单体应用最常用的,其大概流程:
1、用户访问客户端(浏览器),服务器通过session校验用户是否登录
2、 用户没登录返回登录页面,输入账号密码等验证
3、 验证通过创建session,返回sessionId给客户端保存到cookie
4、接着,用户访问其它同域链接,都会校验sessionId,符合就允许访问
ok,简单介绍这套cookie-session机制,之前设计者开发这套机制是为了兼容http的无状态,这套机制有其优点,当然也有一些缺陷:
只适用于B/S架构的软件,对于安卓app等客户端不带cookie的,不能和服务端进行对接
不支持跨域,因为Cookie为了保证安全性,只能允许同域访问,不支持跨域
CSRF攻击,Cookie没做好安全保证,有时候容易被窃取,受到跨站请求伪造的攻击
ok,简单介绍了cookie-session机制后,可以介绍一下jwt的认证
1、用户访问客户端(浏览器、APP等等),服务器通过token校验
2、 用户没登录返回登录页面,输入账号密码等验证
3、 验证通过创建已签名token,返回token给客户端保存,最常见的是存储在localStorage中,但是也可以存在Session Storage和Cookie中
4、接着,用户访问其它链接,都会带上token,服务器解码JWT,如果Token是有效的则处理这个请求
网上对于cookie-session机制和jwt的讨论很多,可以自行网上找资料,我觉得这两套机制各有优点,应该根据场景进行选用,JWT最明显优点就是小巧轻便,安全性也比较好,但是也有其缺点。
比如对于业务繁杂的功能,如果一些信息也丢在jwt的token里,cookie有可能不能保存。
续签问题,jwt不能支持,传统的cookie+session的方案天然的支持续签,但是jwt由于服务端不保存用户状态,因此很难完美解决续签问题
密码重置等问题,jwt因为数据不保存于服务端,如果用户修改密码,不过token还没过期,这种情况,原来的token还是可以访问系统的,这种肯定是不允许的,不过这种情况或许可以通过修改secret实现
6. Java的JJWT实现JWT
6.1 什么是JJWT?
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache
License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界
面,隐藏了它的大部分复杂性。
6.2 实验环境准备
环境准备:
Maven 3.0+
IntelliJ IDEA
技术栈:
SpringBoot2.2.1
Spring Security
新建一个SpringBoot项目,maven加入JJWT相关配置
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>${jjwt.version}version>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>${java.jwt.version}version>
dependency>
pom.xml:
xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.1.RELEASEversion>
<relativePath/>
parent>
<groupId>com.example.springbootgroupId>
<artifactId>springboot-jwtartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springboot-jwtname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<jjwt.version>0.9.0jjwt.version>
<java.jwt.version>3.4.0java.jwt.version>
<mybatis.springboot.version>2.1.1mybatis.springboot.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>${jjwt.version}version>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<v