SpringBoot整合Jwt 前后端分离认证
jwt基本概念不多说了,到处都是,直接上代码
一、生成Jwt
目录结构👇
创建一个普通的springboot 项目: springboot-jwt-demo
只需要加一个Spring Web的模块就ok
接下来咱们导入pom依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
这个依赖是java jwt的依赖,里面帮我们封装了一些功能。咱们拿来即用即可
二、编写一个Jwt的工具包
JwtUtils
package com.xiaozhoubg.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.Base64Utils;
import java.util.Collections;
import java.util.Date;
public class JwtUtils {
//2. 这里调用springboot的base64加密包,加密一个 字符串 ,因为一会我们要解密所以放到外部
private static final byte[] KEY = Base64Utils.encode("Lanou3g".getBytes());
//3. 自定定义一个方法,这样咱们现在只需要传用户id,时间自动3600秒(半小时)
//如果说需要自己定义时间,那就调 1 方法即可。
public static String newToken(Long id){
return newToken(id,10);
}
//1. 创建一个变量 用户id ,过期时间秒 seconds 3600
public static String newToken(Long id,long seconds){
String token = Jwts.builder() //建造者方法
.setId(String.valueOf(id)) //指定id值
.setExpiration(new Date(System.currentTimeMillis() + seconds * 1000))//指定过期时间
.addClaims(Collections.singletonMap("duration",seconds))
.signWith(SignatureAlgorithm.HS256,KEY)//给签名指定算法 和密钥(base64加密的字符串)
.compact();
return token;
}
// 4. 从token中取出id(用户id)
//需要注意时,当token解析不出来的时候该方法会抛出异常(比如假token,过期token)
public static Long getId(String token){
//Claims是负载,也就是token的第二部分。也就是说咱们获取到他的负载
Claims claims = (Claims) Jwts.parser()
.setSigningKey(KEY)
.parse(token)
.getBody();
String id =claims.getId();
return Long.parseLong(id);
}
// 用户登录,后台生成一个token,token返回给用户
// 用户每次请求都携带token,交给后台做验证
// 后台验证之后把token放到响应头再返回给用户
// 用户从响应头中取出token,更新本地的缓存
}
具体的步骤和原因,在代码的注释中包含了
三、测试类测试运行
咱们来编写一个测试类,这里教大家一个小窍门
在方法上面Alt+Enter 然后选生成test方法可以快速生成test方法
@Test
public void newToken() throws InterruptedException {
String token = JwtUtils.newToken(10L, 10);
System.out.println(token);
// 来测试 上面我们设置了token时长10秒
// 接下来咱们睡眠12秒再取,也就是说token过期了咱再取.发现方法抛出了异常
Thread.sleep(12000);
Long id = JwtUtils.getId(token);
System.out.println(id);
}
运行结果肯定是会报Jwt过期异常的,接下来咱们把 线程休眠 Thread.sleep(12000); 注释掉,再启动,控制台成功拿到 id 10
或者说咱们要是不喜欢的话,可以把异常给它抛出,让它返回null
public static Long getId(String token) {
//Claims是负载,也就是token的第二部分。也就是说咱们获取到他的负载
String id = null;
try {
Claims claims = (Claims) Jwts.parser()
.setSigningKey(KEY)
.parse(token)
.getBody();
id = claims.getId();
} catch (ExpiredJwtException e) {
return null;
} catch (MalformedJwtException e) {
return null;
} catch (SignatureException e) {
return null;
} catch (IllegalArgumentException e) {
return null;
}
return Long.parseLong(id);
}
四、编写前端index.html
接下来咱们编写前端,直接在线引用jQuery即可,咱们用它的ajax
resources => static 目录下 创建 index.html (模拟前后端分离)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jwt</title>
<!-- 在线引用jQuery -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<!-- 写一个表单 -->
<form id="login-form" action="/login" method="post">
用户名: <input type="text" name="username">
密码: <input type="text" name="password">
<input type="submit" >
</form>
存好后第二部需要取了,从浏览器缓存取到token
<button id="btn-user-info">获取用户信息</button>
<script>
$('#login-form').submit(function (form) {
//这里要阻止表单默认提交事件
form.preventDefault();
//通过ajax发送登录请求
var username =$('input[name=username]').val();//拿到账户密码然后存到data
var password =$('input[name=password]').val();
var data ={
username:username,
password:password,
}//存到data,下面通过post发送到controller
$.post('/login',data,function (resp) {
console.log(resp)
//登录之后把token存起来,这段代码的意思是将后端传过来的值resp.token传到 浏览器请求头缓存中,命名为token
window.localStorage.setItem("token",resp.token);
});
})
//这里是取token的方法
$('#btn-user-info').click(function () {
// 1.从缓存中取出token,
var token = window.localStorage.getItem('token');
// 2. 发送请求同时,把token放到请求头里
console.log("wote??"+token)
$.ajax({
url:'/info',
type:'get',
headers:{
token:token
},
success:function (resp) {
console.log(resp)
}
})
})
</script>
</body>
</html>
五、编写一个UserController
UserController
这里咱们模拟从数据库中查找出来的数据即可,并不影响什么,大家可以自己去加上数据库
package com.xiaozhoubg.controller;
import com.xiaozhoubg.utils.JwtUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.Map;
@RestController
public class UserController {
//注意,这里咱们不用链接数据库自己定义一个数据做模拟即可,实际开发中再替换成数据库,相信
//大家一定都知道如何替换成数据库。
//登录
@PostMapping("/login")
public Map login(String username,String password){
//接收前端传值并且判断 账户密码
if ("admin".equals(username)&&"admin".equals(password)){
//赋值id=1001
Long id =1001L;
//根据这个id 去 咱们之前的JwtUtils中newToken方法 生成 token
String token = JwtUtils.newToken(id);
//创建好token后return回前端
return Collections.singletonMap("token",token);
}else {
//账户密码错误返回异常
return Collections.singletonMap("erro","用户名或者密码错误");
}
}
//获取token
//@RequestHeader 注解可以去拿请求头
@GetMapping("/info")
public Map userInfo(@RequestHeader String token){
// 根据JwtUtils.getId方法去通过token拿到id,判断id是否正确
if (1001L == JwtUtils.getId(token)){
return Collections.singletonMap("user","admin");
}else {
return Collections.singletonMap("user","token失效");
}
}
}
六、运行测试
ok接下来咱们运行测试一下哈
填写 用户名 密码 然后 点击提交,可以在Application里面看到咱们的请求头中有了token
接下来咱们点击 获取用户信息 在Network可以看到这个请求的请求头携带了token
控制台也打印出来了后台的传值,因为token正确,那如果token错误呢,方法会报个异常,这个异常咱们怎么处理呢?我相信大家有很多方法去处理了,这里就不写了。
成了!
七、总结
本节案例地址
咱们的jwt是有过期时间的,那么怎么来弥补这一点呢??这里给大家一个思路,咱们可以编写一个拦截器,并且在jwt工具类中封装一个方法,这个方法是去判断jwt,去判断jwt剩余的时间,假设剩余时间不足30%了,咱们可以让它重新生成一个新的jwt,咱们每一次请求或特定的请求,都走一遍拦截器,让拦截器去调咱们封装好的 刷新jwt的方法 把这个方法 return 的值加到请求头中。这是一个办法。
这就是jwt啦,大家可以去加上mysql,我这里没加mysql。
当然了,除了Jwt,咱们也可以使用UUID+Redis来实现,把随机生成的字符串保存到Redis中。