JWT的认识及使用
代码:https://github.com/chenchang123abc/jwt_test.git
JWT(Json Web Token)的结构
JWT包含三部分,之间以点(.)连接
- Header(头部)
- Payload(负载)
- Signature(签名)
eyJhbGciOiJIUzI1NiJ9.
eyJqdGkiOiI0ODM0YjVlZC1jODI4LTRmNmMtYTI1Ni1lMTE5NTJkZGYwYWUiLCJpYXQiOjE2MTAxMTgyMDQsImV4cCI6MTYxMDEyMTgwNCwiYWRkcmVzcyI6IuW5v-W3niIsInNleCI6MTIzLCJ1c2VyTmFtZSI6Iua1i-ivlSIsInVzZXJJZCI6MTIzfQ.
0CwxIxVVpzdQUbYAFSnHnnvo_FLJ51gtzfc0l4_uzuc
Header
Header部分是一个json对象,典型的header包含两部分:
- alg:使用的签名算法,比如HMACSHA256或RSA
- typ:token的类型,比如JWT
{
"alg":"HS256",
"typ":"JWT"
}
最后,用Base64Url将这个json对象编码后,作为JWT的第一部分
Payload
Payload部分也是json对象,用来存放数据。JWT有7个官方字段:
- iss(issuer):签发人
- exp(expiration time):过期时间,以秒为单位
- iat(Issued At):签发时间,能够算出JWT的存在时间
- nbf(Not Before):生效时间
- jti(JWT ID):JWT的唯一标识。用来防止JWT重复
- sub(subject):主题(很少使用)
- aud(audience):token的受众(很少被使用)
除了上面字段也可以自定义私有字段,比如:
{
"userId":"1001",
"userName":"张三"
}
最后,用Base64Url将这个json对象编码后,作为JWT的第二部分
Signature
使用Header指定的算法对Header、payload、密钥 三部分进行签名,生成的字符串作为JWT的第三部分。
比如使用HMACSHA256算法进行签名:
HMACHA256(
Base64Url.encode(header)+"."+Base64Url.encode(payload),secret
)
签名可以用来验证数据是否被篡改,而且如果token使用私钥进行了签名,那么该签名还可以验证JWT发送者的身份。
怎么使用JWT?
客户端收到服务器返回的JWT,可以存储在Cookie里面,也可以存储在localStorage。
此后,客户端每次请求服务器,都要带上这个JWT.所以可以把他放在Cookie里面自动发送,但是这样并不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization字段里面。
Authorization:Bearer<token>
另一种做法是,跨域的时候,JWT就放在POST请求的数据体里面。
JWT的特点
- JWT默认是不加密,但是也可以加密。
- JWT不加密的情况下,不能将敏感数据写入JWT (例如:密码)
- JWT不仅可以用于认证,也可以用于交换信息
- JWT最大的缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就说,一旦签发了JWT,在到期之前就会始终有效
- JWT本身包含了认证信息,一旦泄密,任何人都可以获得该令牌的权限。为了减少盗用,JWT的有效期应该设置比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证
- 为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输
和Session-Cookie相比
Session-Cookie方式:客户端每次请求使用cookie携带session_id,服务器根据session_id区分不同的会话。
JWT方式:客户端每次请求都使用请求头携带token,服务器根据token区分不同的客户。
直接上代码操作,简单例子
引入依赖:
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<!--jwt-->
<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<!--单元测试-->
JWT测试例子
public class JwtTest {
//简单的例子进行对jwt说明
@Test
public void JwtDemo(){
//1.生产密钥
String key="0123456789_0123456789_0123456789";//自定义一个字符串(字符串不能太短,不然报错)
SecretKey secretKey=new SecretKeySpec(key.getBytes(), SignatureAlgorithm.HS256.getJcaName());
//2.生成token
String token= Jwts.builder() // 创建jwt对象
.setSubject("Json Web Token")//设置主题(声明信息)
.signWith(secretKey) //设置安全密钥(生产签名所需要的密钥和算法)
.compact(); //生成token(1.header和payload 2.生成签名 3.拼接字符串)
System.out.println(token);
//3.验证token
try{
Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
System.out.println("验证成功");
}catch(Exception e)
{
System.out.println("验证失败");
}
//4.解析token
Claims body=Jwts.parser() //创建解析对象
.setSigningKey(secretKey)//设置安全密钥(生成签名所需的密钥和算法)
.parseClaimsJws(token) //解析token
.getBody(); //获取payload部分内容
System.out.println("setSubject:"+body);
}
}
创建JWT的工具类
/*
jwt工具类
*/
public class JwtUtils {
//key:按照签名算法的字节长度设置key
private final static String SECRET_KEY="0123456789_0123456789_0123456789";
//过期时间
private final static long TOKEN_EXPIRE_MILLIS=1000*60*60;
//创建token
public static String createToken(Map<String,Object> claimMap){
long currentTimeMillis=System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTimeMillis))
.setExpiration(new Date(currentTimeMillis+TOKEN_EXPIRE_MILLIS))
.addClaims(claimMap)
.signWith(generateKey())
.compact();
}
//验证token
public static String verifyToken(String token){
try{
Jwts.parser().setSigningKey(generateKey()).parseClaimsJws(token);
return "验证成功";
}catch (Exception e)
{
e.printStackTrace();
return "验证失败";
}
}
//解析token
public static Map<String,Object> parseToken(String token){
return Jwts.parser()
.setSigningKey(generateKey())
.parseClaimsJws(token)
.getBody();
}
//生成token
public static Key generateKey(){
return new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}
}
使用工具类测试:
@Test
public void JwtDome2(){
Map<String, Object> map = new HashMap<String, Object>();
map.put("userId", 1001);
map.put("userName", "张三");
map.put("age",20);
String token = JwtUtils.createToken(map);
System.out.println(token);
}
token:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4YWQwMThmNS05NWU1LTQ5NzEtYjZhMi1lNTQ1Y2EzYTM4YmYiLCJpYXQiOjE2MTAxNjY4ODMsImV4cCI6MTYxMDE3MDQ4MywidXNlck5hbWUiOiLlvKDkuIkiLCJ1c2VySWQiOjEwMDEsImFnZSI6MjB9.7fDzkHZ8NTfEwtSasXa0xyp-7zBFfkGFmeKA84OAni0
@Test
public void JwtDome2(){
// Map<String, Object> map = new HashMap<String, Object>();
// map.put("userId", 1001);
// map.put("userName", "张三");
// map.put("age",20);
// String token = JwtUtils.createToken(map);
// System.out.println(token);
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4YWQwMThmNS05NWU1LTQ5NzEtYjZhMi1lNTQ1Y2EzYTM4YmYiLCJpYXQiOjE2MTAxNjY4ODMsImV4cCI6MTYxMDE3MDQ4MywidXNlck5hbWUiOiLlvKDkuIkiLCJ1c2VySWQiOjEwMDEsImFnZSI6MjB9.7fDzkHZ8NTfEwtSasXa0xyp-7zBFfkGFmeKA84OAni0
String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4YWQwMThmNS05NWU1LTQ5NzEtYjZhMi1lNTQ1Y2EzYTM4YmYiLCJpYXQiOjE2MTAxNjY4ODMsImV4cCI6MTYxMDE3MDQ4MywidXNlck5hbWUiOiLlvKDkuIkiLCJ1c2VySWQiOjEwMDEsImFnZSI6MjB9.7fDzkHZ8NTfEwtSasXa0xyp-7zBFfkGFmeKA84OAni0";
String result = JwtUtils.verifyToken(token);
System.out.println(result);
Map<String,Object> map=JwtUtils.parseToken(token);
System.out.println(map);
}
输出结果:
验证成功
{jti=8ad018f5-95e5-4971-b6a2-e545ca3a38bf, iat=1610166883, exp=1610170483, userName=张三, userId=1001, age=20}