springboot集成JWT生成token
在学习之前我假设你已经了解过token
阅读前必看:
如果你已经有了相关token基础只想学习springboot集成JWT请点击下面链接跳转到文章相应描述处我要直接学习springboot集成JWT
如果你是小白那么我建议你应该重头阅读的当然了你也可以直接点击我要直接学习springboot集成JWT但是我并不建议噢
什么是JWT?(这是来自官方的翻译噢):
jwt是一个开放标准(rfx7519),定义了一种紧凑、自包含的方式,用于在各方之间json对象安全传输信息,此信息可以验证和信任,因为它是数字签名的,jwt可以使用秘密(使用HMAC算法) 或使用RSA或者ECDSA的公钥/钥 对进行签名通俗易懂的解释的话 jwt就是通过json形式作为web应用中的令牌 用于各方之间安全将信息作为json对象传输 在数据传输过程中还可以完成数据加密、签名等相关处理# 学习目标:
JWT能做什么?:
1.【授权】
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
2.【信息交换】
-JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
我为什么要使用JWT?:
先看一下传统的Session认证方式
【认证方式】
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
【暴露的问题】
-1.每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
-2.用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
-3.因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
-4.在前后端分离系统中就更加痛苦:如下图所示王也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻击)攻击, session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。
JWT结构:
先看一下传统的Session认证方式
【令牌组成】
1.标头(Header)
2.有效载荷
3.签名(Signature)
因此,JWT通常如下所示:xxxx.yyy.zzzzz Header.Payload.Signature
【Header】
1.标头通常由两部分组成: 令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分
2.注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
"alg" : "HS256",
"typ" : "JWT"
【Payload】
1.令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分
"sub" : "1234578",
"name" : "John Doe"
"admin" : true
【Signature】
1.前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header和 payload以及我们提供的一个密钥,然后使用header 中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过 如:
HMACSHA256(base64UrlEncode(header) + "." + base64Ur1Encode(payload) , secret) ;
【签名目的】
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
在负载里面不要加入任何敏感的数据!!!
在负载里面不要加入任何敏感的数据!!!
在负载里面不要加入任何敏感的数据!!!
这里先运行一个第一个JWT程序(普通javaMaven项目)
环境: (版本测试时间202114/19 版本无异常)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
test测试
@Test
public void contextLoads() {
//生成一个token并存储
HashMap<String, Object> map = new HashMap<>(); //自定义header
Calendar insCalendar = Calendar.getInstance();
insCalendar.add(Calendar.SECOND, 800); //存储时间为800s
String sign = JWT.create()
.withHeader(map) // header 可以没有 有默认值
.withClaim("username", "小爱") // payload
.withClaim("userage", 18) // payload
.withExpiresAt(insCalendar.getTime()) // 指令令牌过期时间
.sign(Algorithm.HMAC256("$J#F@f@G!D")); // 签名
System.out.println("拿到的token:" + sign);
}
//根据拿到的token去查找存的值
@Test
public void contextLoads1() {
//创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("$J#F@f@G!D")).build();
DecodedJWT verify = jwtVerifier.verify(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTg4MTkyODgsInVzZXJhZ2UiOjE4LCJ1c2VybmFtZSI6IuWwj-eIsSJ9.2pfRM8uRiiWH_N9q9_cO3mDOy05d-Do3UrI8bioAHoA");
System.out.println(verify.getClaim("username").asString());
System.out.println(verify.getClaim("userage").asInt());
System.out.println("token过期时间:"+verify.getExpiresAt());
}
测试结果:
小爱
18
token过期时间:Mon Apr 19 16:01:28 CST 2021
//时间因为博主不是截的第一次运行的实时间所以不准确
拿到的token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTg4MTkzMjAsInVzZXJhZ2UiOjE4LCJ1c2VybmFtZSI6IuWwj-eIsSJ9.zPENnAy2XWQDSubOqDDYkHPpCsFfSK2iRejFqrk7sII
注意: 存int 用asInt String 用asString
注意: 存int 用asInt String 用asString
注意: 存int 用asInt String 用asString
【常见异常信息】
- SignatureVerificationException :
签名不一致异常
- TokenExpiredException:
令牌过期异常
- AlgorithmMismatchException:
算法不匹配异常
- InvalidclaimException:
失效的payload异常
开始本节文章的重头戏~~~~
以下内容假设你已经有了springboot基础
温馨提示:预览如果不方便的话我推荐两个方案
①你可以打开代码编辑器查看
②下载Notepad进行代码查看 下载传送门
1. 创建操作JWT的工具类
(这一步博主做了简单的解读你可以仔细阅读并查阅相关资料深入理解)
你可以查看JWT官方文档传送门
package com.jwt.text;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
public class JwtUtil {
// 签名不应该泄露给他人非常危险 这一点要非常注意
private static String secret = "!@#$%lijihao!@#$%+%$#@!yangding%$#@!+lijihaoyangding";
/**
*
* 生成token
*
* @param hashmap
*/
public static String InserToken(Map<String, String> hashmap) {
Calendar insCalendar = Calendar.getInstance(); // 提供日期格式的处理
insCalendar.add(Calendar.DATE, 7); // 单位为天 默认7天后过期
JWTCreator.Builder builder = JWT.create(); // 创建JWT builder
hashmap.forEach((k, v) -> { // 将传入的值循环插入
builder.withClaim(k, v);
});
String token = builder.withExpiresAt(insCalendar.getTime()) // 指定指令令牌过期时间 上面指定时间单位为天 单位可以自行更改
.sign(Algorithm.HMAC256(secret)); // 传入上面定义的secret
return token; // 最后将生成的token返回
}
/**
* 验证token合法性
*
*/
public static DecodedJWT SelectToken(String token) { // 验证合法性此时报错就是没有通过验证
// 为你列出常见的报错
/**
* - SignatureVerificationException : 签名不一致异常 - TokenExpiredException: 令牌过期异常 -
* AlgorithmMismatchException: 算法不匹配异常 - InvalidclaimException: 失效的payload异常
*
*/
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build(); // 传入签名
DecodedJWT verify = jwtVerifier.verify(token);// 传入token
return verify; // 最后将生成的verify对象返回
}
}
2. springboot整合JWT操作token
本次用登录操作来模拟存取token 因为重点在于操作token所以暂时不访问数据库
package com.baomidou.ant.mp.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.ant.mp.entity.Xinwen;
import com.jwt.text.JwtUtil;
import ch.qos.logback.core.encoder.EchoEncoder;
@RestController
@RequestMapping("/Jwt")
public class JwtController {
/**
* 登录操作 生成登录验证成功后给用户颁发 唯一token
* @param name
* @param pwd
* @return
*/
@RequestMapping("/jwt1")
public Map<String,String> login(String name,String pwd){ //传入的账号密码
Map<String,String> map=new HashMap<String,String>();
System.out.println("当前用户名(唯一):"+name+"密码"+pwd);
if(name.equals("123")&&pwd.equals("456")) { //通过登录验证
Map<String,String> mapJwt=new HashMap<String,String>(); //传入工具类的参数
mapJwt.put("name",name);
String token=JwtUtil.InserToken(mapJwt); //拿到生成的token
map.put("static", "登录成功正在签发token");
map.put("tokenvalue", token);
}else {
map.put("static", "登录失败!签发token失败");
}
return map;
}
/**
*
* 访问网站普通接口操作 验证用户token
* @param token
* @return
*/
@RequestMapping("/jwt2")
public Map<String,String> selectlogin(String token){ //传入用户唯一token
Map<String,String> map=new HashMap<String,String>();
try {
DecodedJWT de= JwtUtil.SelectToken(token);
map.put("static", "token验证成功!");
return map;
} catch (SignatureVerificationException e) {
e.printStackTrace();
} catch (TokenExpiredException e) {
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
登录接口(login方法)测试:
普通接口(selectlogin方法)测试:
大功告成 完结撒花·~~~·~~~~~~~~~~~~~~~~~~~~~~掌声送给你!