有关session存储用户信息在spring系列springsession文章中有写,session缺点是占用服务器资源,配置多台服务器后又需要对session进行统一存储(redis),保证每台服务器都可以取到正确的session。
JWT不用缓存数据库redis来实现用户信息的共享,也可以达到一次登录,处处可见
JWT全称JSON Web Token,实现过程简单的说就是用户登录成功之后,将用户的信息进行加密,然后生成一个token返回给客户端,与传统的session交互没太大区别。省掉了redis,把用户信息存到token中,这样客户端、服务端都可以从token中获取用户的基本信息,既然客户端可以获取,肯定是不能存放敏感信息的,因为浏览器可以直接从token获取用户信息。
交互流程:
文章图片
1.JWT具体内容 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjU0MjIzMzAwLCJpYXQiOjE2NTQyMjMyNzAsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.pO0gpz6AuDFtBAEOlTV09-BJVIIxqSGP-k_fcDrVhdw
1.header(头部)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
使用base64加密,用于存放token类型和加密协议.
文章图片
2.payload(载荷)
eyJpZCI6MSwiZXhwIjoxNjU0MjIzMzAwLCJpYXQiOjE2NTQyMjMyNzAsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ
存放有效信息
文章图片
3.signature
pO0gpz6AuDFtBAEOlTV09-BJVIIxqSGP-k_fcDrVhdw
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
2.springboot整合
1.pom
com.auth0
java-jwt
3.7.0
2.用户实体类
@Data
public class User {
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
3.TokenUtil工具类
用于创建、获取、验证Token
@Slf4j
@Component
public class TokenUtil {//密钥
public static final String SECRET = "youareapig??shabixiangpojie?";
//过期时间:秒
public static final int EXPIRE = 30;
/**
* 生成Token
*/
public static String createToken(User user){
Calendar nowTime = Calendar.getInstance();
//过期时间
nowTime.add(Calendar.SECOND, EXPIRE);
Date expireDate = nowTime.getTime();
String token = JWT.create()
//这是在设置第二部分信息,不要设置密码之类的,因为这些信息可以通过浏览器获取
//用户id
.withClaim("id", user.getId())
//用户名
.withClaim("username",user.getUsername())
//创建token的时间
.withIssuedAt(new Date())//签名时间
//设置token的过期时间
.withExpiresAt(expireDate)//过期时间
//设置第一部分
.sign(Algorithm.HMAC256(SECRET));
//签名
return token;
}/**
* 验证token
*/
public static DecodedJWT verify(String token) {
//如果有任何验证异常,此处都会抛出异常 我们需要在拦截器调用这个方法,捕获异常,然后返回错误信息给前端
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
return decodedJWT;
}/**
* 获取token中的 payload 也就是第二部分的信息
*/
public static DecodedJWT getTokenInfo(String token) {
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
//使用 TokenUtils.getTokenInfo(token).getClaim("account").asString()
return decodedJWT;
}
}
4.拦截器
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
System.out.println("进入拦截器");
//实际这个名字可以指定为别的,token太没有辨识度---
//这个header是在创建完token返回给前端时指定的头部的key,vakue就是token内容
String token=httpServletRequest.getHeader("token");
Map map = new HashMap<>();
try {
//这里尽行token验证,捕获异常,正常的话也不需要处理,直接抛出异常,由统一异常处理类进行处理,然后返回给前端统一数据类型。
TokenUtil.verify(token);
return true;
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg", "签名不一致");
map.put("code",500);
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg", "令牌过期");
map.put("code",500);
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg", "算法不匹配");
map.put("code",500);
} catch (InvalidClaimException e) {
e.printStackTrace();
map.put("msg", "失效的payload");
map.put("code",500);
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "token无效");
map.put("code",500);
}
//根据自己所需选择所需的异常处理
map.put("state", false);
//响应到前台: 将map转为json
String json = new ObjectMapper().writeValueAsString(map);
httpServletResponse.setContentType("application/json;
charset=UTF-8");
httpServletResponse.getWriter().println(json);
return false;
}
}
5.注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Override
public void addInterceptors(InterceptorRegistry registry) {
List excludePathLists= new ArrayList<>();
//注册、登录允许访问,不进行拦截
excludePathLists.add("/user/login");
excludePathLists.add("/user/register");
excludePathLists.add("/user/info");
registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePathLists);
}
}
参考链接
6.创建controller进行测试
@RestController
public class UserController {
//模拟登录
@PostMapping("/user/login")
public String LoginUser(HttpServletResponse servletResponse){
//这里正常前端会传进来用户信息,然后去数据库查询,能查到,并且账号密码匹配成功,则可以登录。
//这里只是模拟,随便创建了一个用户信息
User user= new User();
user.setUsername("zhangsan");
user.setPassword("123456");
user.setId(1L);
//根据用户信息创建token,(登录成功的处理逻辑)
String myToken = TokenUtil.createToken(user);
//设置header
servletResponse.setHeader("token",myToken);
//获取token,获取过期时间进行返回
DecodedJWT tokenInfo = TokenUtil.getTokenInfo(myToken);
Date expiresAt = tokenInfo.getExpiresAt();
return expiresAt.toString();
}
//测试其他账号登录
@PostMapping("/zhangsan")
public String testLogin(){
return "ok";
}
}
7.postman测试
【springboot|SpringBoot集成JWT实现token验证】1.最一开始还没登录过,肯定访问不到。
文章图片
2.模拟登录成功,返回了过期时间
文章图片
携带的header
文章图片
3.再测试其他用户登录
携带token请求头
文章图片
测试过期
文章图片
至此一个简单的token登录机制就实现了~
推荐阅读
- 全文检索|springboot整合
- 全文检索|elasticsearch&Kibana安装
- 全文检索|初步检索及进阶检索
- 软件测试|详解软件测试中白盒测试基本概念及四种白盒测试方法
- 运维|jenkins构建maven、git项目部署远程服务器完整过程
- Java修炼之旅|jenkins自动部署maven项目
- java|类和对象(上)——JavaSE
- J2EE|jsp标签
- Java|Spring Cloud微服务分布式架构之组件和概念介绍