什么是jwt?
JWT:JSON Web Token,作为JSON对象在各方之间传输安全信息,该信息可以被验证和信任,因为它是数字签名的。在前后端分离的项目中,用户需要被授权才可进行某些操作,这就需要jwt的应用。且使用jwt可以减少频繁查询数据库,减轻服务器压力。
大体思路
- 前端发起登录请求,后端接收到用户信息
- 后端验证用户信息成功后,为前端返回一个token值
- 前端回调函数接收到token后,将其存储到vuex和localStorage中
- 前端每次路由跳转,都需要携带token发起验证
如何实现?
- 后端(Java)需要分别实现两个方法:createToken(创建)和checkToken(验证),一般将其封装为静态的工具类。token的有效期由该类定义。
public class JwtUtil {
private static long time = 1000 * 60 * 60 * 24;//token有效期:24小时
private static String signature = "admin";
public static String createToken() {
JwtBuilder jwtBuilder = Jwts.builder();
String jwtToken = jwtBuilder
//header
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//payload
.claim("username", "admin")
.claim("role", "admin")
.setSubject("admin-test")
.setExpiration(new Date(System.currentTimeMillis() + time))
.setId(UUID.randomUUID().toString())
//signature
.signWith(SignatureAlgorithm.HS256, signature)
.compact();
return jwtToken;
}
public static boolean checkToken(String token) {
if (token == null) {
return false;
}
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signature).parseClaimsJws(token);
} catch (Exception e) {
return false;
}
return true;
}
}
- 后端分别为前端提供两个API:/login和/checkToken,分别用于返回token(登录时)以及验证token
@PostMapping("/login")
public LoginResult login(@RequestBody User user) {
User result_user = userService.login(user.getUsername(), user.getPassword());
if (result_user == null) {
return LoginResult.fail("登录失败,用户名或密码错误");
} else {
return LoginResult.success(result_user, JwtUtil.createToken());
}
}
@GetMapping("/checkToken")
public Boolean checkToken(HttpServletRequest request){
String token = request.getHeader("token");
return JwtUtil.checkToken(token);
}
- 前端(vue)在vuex中定义token、setToken和getToken方法,setToken时需要将token存储到localStorage中
export default new Vuex.Store({
state: {
token: '',
},
mutations: {
//存储token方法
//设置token等于外部传递进来的值
setToken(state, token) {
state.token = token
localStorage.token = token //同步存储token至localStorage
},
},
getters: {
//获取token方法
//判断是否有token,如果没有重新赋值,返回给state的token
getToken(state) {
if (!state.token) {
state.token = localStorage.getItem('token')
}
return state.token
},
},
})
- 前端使用axios发起登录请求,并在回调函数中接收到后端传回的token,此时调用store中的commit方法存储token
login() {
this.$axios
.post("/login", this.user)
.then((res) => {
if (res.data.data != null) {
this.$store.commit("setToken", res.data.token);
this.$router.push("/home");
this.$message({
type: "success",
message: "登录成功",
});
} else {
this.$message.error("密码错误或账号不存在");
}
})
.catch(() => {
this.$message.error("服务器繁忙,请稍后再试");
});
},
- 前端定义路由守卫,在每一次路由跳转时发起/checkToken请求,验证token是否有效,当token过期时用户就需要重新登录了。
//路由守卫,发起token验证,限制页面访问
router.beforeEach((to, from, next) => {
//获取token
let token = store.getters.getToken
if (token == "") {//如果没有token
if (to.path == "/") {//如果已经是登录页
next()
} else {//否则不是登录页
next({ path: '/' })//跳转到登录页
}
} else {//如果有token,就校验token合法性
axios({
url: '/checkToken',
method: 'get',
headers: {
token: token
}
}).then((response) => {
if (!response.data) {
store.commit("setToken", "")//校验失败,移除token
next({ path: '/' })
}
})
next()
}
})