快速入门JWT+Redis实现Token验证!
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
导言:该文最终本人实现目的为通过JWT来生成token作为用户登录或调用api等身份验证凭证,同时简单的引入了Redis作为缓存来存放Token。用最简洁的方法给第一次接触Token的朋友们快速入门并且实际运用到项目中。不纠结于理论,只聚焦于实战,如果有希望了解更多理论的朋友,可以在看本文的过程中查询其他理论性文章。
这几天毕设项目正准备开始搞,想到之前的项目登录没有做token密码只是md5加密,再加上实习导师的建议,我就开始搞了JWT+Redis,查了诸多参考文献资料等,花了三天时间基本完成了这个功能。
Json Web Token
引入JWT
<!-- Json web token 用于验证-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
配置JWT工具类
Token分为三个部分,头,荷载,和签名,我们需要对这几个部分分别设置,第一个方法为创建一个JsonWebToken
第二个工具类为检查token
是否正常,值得注意的是,除了解析后的姓名身份等等,如果时间异常(已经超过有效期)也将被catch
异常,所以直接写在try-catch
中即可
//JsonWebTokenUtil
public class JsonWebToken {
private static longtime= 1000*60*60*24;
private static Stringsignature= "9991morF0GgnahZ";
public static String createToken(String role,String name){
JwtBuilder jwtBuilder = Jwts.builder();
String jwToken = jwtBuilder
//链式编程 添加头
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","HS256")
//payload 载荷
.claim("name",name)
.claim("role",role)
//主题
.setSubject("admin")
//有效期
.setExpiration(new Date(System.currentTimeMillis()+time))
.setId(UUID.randomUUID().toString())
//signature签名
.signWith(SignatureAlgorithm.HS256,signature)
//拼接前面三个
.compact();
return jwToken;
}
public static boolean checkToken(String token){
if (Objects.equals(token, "") || token == null){
return false;
}
try {
//解析后拿到存有token信息的集合
Jws<Claims> claimsJws1 = Jwts.parser().setSigningKey(signature).parseClaimsJws(token);
} catch (Exception e) {
System.out.println("【Error】token解密异常");
e.printStackTrace();
return false;
}
return true;
// Claims claims = claimsJws1.getBody();
// System.out.println(claims.get("name"));
// System.out.println(claims.get("role"));
// System.out.println(claims.getId());
}
}
在application.yml中
spring:
redis:#配置redis
host: 127.0.0.1#公网ip
port: 6379#端口一般为6379
connect-timeout: 5000
database: 1#数据库
在控制层对应URL中使用
只展示与Token相关核心代码
adminLogin
为登录VO内有phone password token三个字段,最后返回时将password清空保证安全(并非唯一方式也并非最佳方式此返回仅供参考)
getOne
为本人写的获取用户信息的函数,并非本文重点内容,在此声明不做阐述。
//声明redis
@Autowired
private StringRedisTemplate stringRedisTemplate ;
String name = getOne(queryWrapper).getAdminName();
//下面为核心代码
//获取Token
adminLogin.setToken(JsonWebToken.createToken("admin",name));
//将用户token写入Redis缓存 以token为key与value前端不用多传值,后两个参数为过期时间
stringRedisTemplate.opsForValue().set(adminLogin.getToken(),adminLogin.getToken(),1000*60*60*24*7, TimeUnit.SECONDS);
//上面为核心代码
adminLogin.setPassword("");
return JsonResultUtils.successMsg("登录成功",adminLogin);
完成到这一步之后,恭喜你已经完成了基础的token配置(可以配合解析token直接用了)但是,一般来说我们用户使用过程中会让有些功能需要token,有些功能不需要token,那具体那些需要哪些不需要呢?我们需要在后台做一个URL的区分,这个时候就需要用到拦截器
拦截器的配置
第一步需要先写好拦截的控制方法
在下面代码中已有对应的详细注释,只对个别点做单独说明
Authorization
为前端设置的在请求头中放置的token值,后续会有说明前端如何设置
@Component
public class TokenHandler implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 注意:放行浏览器的预检请求
System.out.println("进入预检");
if (request.getMethod().equals("OPTIONS")) {
return true;
}
// 获取请求头中携带的token值
String token = request.getHeader("Authorization");
// token验证
//查看token是否为空 查看token是否合法
if(JsonWebToken.checkToken(token)) {
//查看Redis中有无token
String value = stringRedisTemplate.opsForValue().get(token);
if (Objects.equals(value, token)) {
System.out.println("【success】token与redis中的匹配成功");
return true;
}
}
System.out.println("【error】 token与redis中的匹配失败");
// throw new Exception("【error】 token与redis中的匹配失败");
// 验证失败,拦截请求
return false;
}
}
第二步我们需要去写好拦截的配置
第一个方法为拦截器方法,在注释中已经讲的很明白了,如有多个排除拦截URL,用逗号分隔,或者写在List中
第二个方法为跨域,反正你先这样配上,控制层用注解去跨域,因为根据查阅的部分资料文章说这个跨域可能会影响token,我没碰到过不清楚,反正先配上hh
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TokenHandler tokenHandler;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加tokenHandler拦截器 全部拦截 排除"/admin/login"
registry.addInterceptor(tokenHandler).addPathPatterns("/**").excludePathPatterns("/admin/login");
}
//跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许跨域请求的地址
.allowedOriginPatterns("*") // 允许跨域请求的域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATH") // 允许请求的方式
.allowCredentials(true) // 是否允许证书(cookies)
.maxAge(3000); // 跨域允许时间
}
}
至此为止你的token就已经大功告成了!
前端配置!
配置axios的request方法中拦截request方法
注意:如果你出错了,请检查你是否拿到token时把其存入了localStorage!
Request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
// 在浏览器的 Session Storage 中拿到 token 值
config.headers.Authorization =window.localStorage.getItem("token");
return config;
})
Redis
引入Redis启动器
<!-- 配置使用redis启动器,存储token -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
如果出错!请想一想你装Redis没,装了开没,开了ping没,ping了pong没。
如果一切正常但是依旧报错,请移步搜索,因为我没踩过坑…
本文尚未设计点
- 前端Router路由时是否需要拦截? 解决方案:设置路由守卫,后端新开一个checktoken的api,跳转时询问后端。
- 相信不完美的地方,尚未考虑到的地方还有很多,请大家多多指教,将持续改进更新本文。
转载自:https://juejin.cn/post/7144194493804183565