一、业务场景
前后端分离或者跨系统的用户认证
二、JWT简介
批里批里
JWT认证原理
三、环境搭建
引入依赖即可
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
四、简单的测试一哈
1.生成JWT
使用JWT.create()方法生成JWT,JWT由三部分组成,及头,负载的信息和签名。头部我们使用JWT的默认算法类型,所以不做设置,使用withClaim方法设置负载信息,使用sign设置签名,Algorithm.HMAC256()是最常用的加密算法,签名要自己保存好。
@Test
public void testJWT()
{
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND,60);//设置过期时间
String token = JWT.create().withClaim("userid", 1).
withClaim("nickname", "张大炮").
withExpiresAt(c.getTime()).
sign(Algorithm.HMAC256("xue$li!@#angqwe"));
System.out.println(token);
}
得到的token
2.验证JWT
@Test
public void testJWTAH()
{
//创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("xue$li!@#angqwe")).build();
//得到解码对象
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuaWNrbmFtZSI6IuW8oOWkp-eCriIsImV4cCI6MTYwMjMwNzg1NCwidXNlcmlkIjoxfQ.rrhH_KoWekdqPhJQU2H7FGDiicgVkDcOGmeEBkju-_0");
//得到负载的信息
System.out.println(verify.getClaim("nickname").asString());
}
五、封装成一个util
带火可以根据自己的喜欢自己封装哈
下面这个是我自己封装的
@Component
public class JwtUtil {
//由配置文件得到签名
private static String sign;
//从配置中抽取数据注入静态变量
@Value(value = "${jwt.sign}")
public void setSign(String Sign)
{
sign = Sign;
}
指定负载信息获得Token
public static String getToken(Map<String,String> payload)
{
//创建一个JWTBuilder
JWTCreator.Builder tokenBuilder = JWT.create();
payload.forEach((k,v)->{
tokenBuilder.withClaim(k,v);
});
//生成签名后的token
String token = tokenBuilder.sign(Algorithm.HMAC256(JwtUtil.sign));
return token;
}
public static DecodedJWT verify(String token) throws Exception
{
//指定签名。获得解码器
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(sign)).build();
//得到解码后的对象,如果通过不了验证就会抛出异常
DecodedJWT verify = jwtVerifier.verify(token);
return verify;
}
}
六、编写业务
结合了正在写的项目,可以只关注与JWT有关的
验证方法
@GetMapping("/islogin")
public R islogin(@RequestHeader("Authorization") String Authorization)
{
try {
//验证通过,验证不通过抛出异常
DecodedJWT verify = JwtUtil.verify(Authorization);
String userId = verify.getClaim("userId").asString();
String nickname = verify.getClaim("nickname").asString();
Map<String,String> payload = new HashMap<>();
payload.put("userId",userId);
payload.put("nickname",nickname);
//带上我要的负载信息
return R.ok().put("payload",payload);
}
catch (SignatureVerificationException e)
{
//验证失败,直接返回错误信息
R.error("无效签名");
e.printStackTrace();
}
catch (TokenExpiredException e )
{
//验证失败,直接返回错误信息
R.error("token过期");
e.printStackTrace();
}
catch (AlgorithmMismatchException e)
{
//验证失败,直接返回错误信息
R.error("token算法不一致");
e.printStackTrace();
}
catch (Exception e)
{
//验证失败,直接返回错误信息
R.error("无效token");
e.printStackTrace();
}
return R.error("未知错误");
}
AOP模块(单项目应该用拦截器实现,也是AOP的思想,分布式的话用网关实现,这里我就顺手把之前的代码改了一点)
@Pointcut("@annotation(com.zhangxueliang.common.annotation.LoginJudge)")
public void pointcut()
{
}
//验证Token信息
@Around("pointcut()")
public R isLogin(ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget().getClass().getName();
Object[] args = joinPoint.getArgs();
//约定带有LoginJudge切点的目标方法 第一个参数为token:Authorization,第二个参数为负载信息Payload
String Authorization = args[0].toString();
R res = authFeign.islogin(Authorization);
if (!res.get("code").equals(0))
{
return R.error("未登录");
}
else {
//获得JWT负载信息
System.out.println(res);
HashMap<String,String> payload =(HashMap) res.get("payload");
System.out.println(payload);
args[1] = payload;
Object object = joinPoint.proceed(args);
return (R)object;
}
}
业务模块
@RequestMapping("/save")
@LoginJudge
public R save(@RequestHeader("Authorization") String authorization,
Map<String,String> payload, @RequestBody OrderEntity order){
System.out.printf(payload.get("userId"));
System.out.println(payload.get("nickname"));
R res = productFegin.updateStatus(order.getSku_id());//尝试修商品状态
if (res.get("code") .equals(0) ) {
order.setOrderSn(IdWorker.getTimeId());//生成订单号
order.setCreateTime(new Date());//生成创建时间
order.setStatus(0);
boolean success = orderService.save(order);//创建订单
if (success) {
//放入死信队列
rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order);
return R.ok();
} else {
//TODO 通知修改商品状态
return R.error("未知原因,创建订单失败");
}
}
else if(res.get("code").equals(404101)) return R.error("商品不可被拍下");
else return R.error("商品信息已改变");
}