JWT学习笔记_下篇
本文基于B站UP主 【编程不良人】 视频教程 【 JWT认证原理、流程整合springboot实战应用,前后端分离认证的解决方案】 进行整理记录,仅用于个人学习/交流使用
视频链接:https://www.bilibili.com/video/BV1i54y1m7cP
官方资料:http://www.baizhiedu.xin
JWT学习笔记上篇:https://blog.csdn.net/A233666/article/details/113243370
JWT学习笔记下篇:https://blog.csdn.net/A233666/article/details/113243454
目录标题
六、在SpringBoot中使用JWT
引入依赖
<!--JWT的依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
生成Token
@Test
void testJWT(){
//构造map
HashMap<String,Object> map=new HashMap<>();
Calendar instance=Calendar.getInstance();//获取日历对象
instance.add(Calendar.SECOND,80);//设置时间80秒
//生成令牌
String token= JWT.create()
.withHeader(map)//header
.withClaim("name","zhangsan")
.withClaim("age",13)
.withExpiresAt(instance.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256("!Q@W#E$RSAW&&"));//签名
System.out.println(token);
}
输出结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2MTE2NDQ4MTQsImFnZSI6MTN9.cGHQa-0L7fkozyU9i_Wn9CXP76chqgqLqcpwsxrACFM
根据令牌和签名解析数据
@Test
void testJWT02(){
JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256("!Q@W#E$RSAW&&")).build();
DecodedJWT verify=jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2MTE2NDQ4MTQsImFnZSI6MTN9.cGHQa-0L7fkozyU9i_Wn9CXP76chqgqLqcpwsxrACFM");
System.out.println(verify.getClaim("name").asString());
System.out.println(verify.getClaim("age").asInt());
//第二种获取信息的方法
System.out.println(verify.getClaims().get("name").asString());
System.out.println(verify.getClaims().get("age").asInt());
//获取基本信息
System.out.println("过期时间 = "+verify.getExpiresAt());
System.out.println("header = "+verify.getHeader());
System.out.println("payload = "+verify.getPayload());
System.out.println("signature = " + verify.getSignature());
System.out.println("token = " + verify.getToken());
System.out.println("algorithm = " + verify.getAlgorithm());
}
输出结果:
zhangsan
13
zhangsan
13
过期时间 = Tue Jan 26 15:06:54 CST 2021
header = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload = eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2MTE2NDQ4MTQsImFnZSI6MTN9
signature = cGHQa-0L7fkozyU9i_Wn9CXP76chqgqLqcpwsxrACFM
token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2MTE2NDQ4MTQsImFnZSI6MTN9.cGHQa-0L7fkozyU9i_Wn9CXP76chqgqLqcpwsxrACFM
algorithm = HS256
常见的异常信息:
- SignatureVerificationException:签名不一致异常
- TokenExpiredException:令牌过期异常
- AlgorithmMismatchException:算法不匹配异常
- InvalidclaimException:失效的payload异常
JWT工具类封装
package com.lut.redisspringboot.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JWTUtils {
private static final String SING="@$%^$#REWw@#%WA12)*(F";
/**
* 生成token
* @param map
* @return
*/
public static String getToken(Map<String,String> map){
Calendar instance=Calendar.getInstance();//获取日历对象
instance.add(Calendar.DATE,7);//默认七天过期
JWTCreator.Builder builder= JWT.create();//创建 jwt builder
//EL表达式 存放数据
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token=builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
return token;
}
/**
* 验证token合法性
* 如果有错误会报错,获取不到信息
*/
public static DecodedJWT decodedToken(String token){
DecodedJWT verify=JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
return verify;
}
}
七、整合SpringBoot
基本使用
7.1、引入依赖
<!--JWT的依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!--引入mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--引入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<!--引入mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
7.2、配置文件配置:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?useSSL=false&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.lxf.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
logging.level.com.lxf.dao=debug
7.3、新建数据库
新建数据库表,添加数据
7.4、新建实体类
package com.lut.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private Integer id;
private String name;
private String password;
}
7.5、dao层
package com.lut.dao;
import com.lut.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao {
/**
* 登录方法
* @param user
* @return
*/
User login(User user);
}
7.6、UserDao.xml文件
注意: <mapper namespace=“com.lut.dao.UserDao”> 路径要正确
在application.properties中设置的路径也要正确
mybatis.type-aliases-package=com.lut.entity
mybatis.mapper-locations=classpath:mapper/*.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lut.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select id,name,password from user where name=#{name} and password=#{password}
</select>
</mapper>
7.7、service层
package com.lut.service;
import com.lut.entity.User;
import org.springframework.stereotype.Service;
@Service
public interface UserService {
/**
* 登录方法
* @param user
* @return
*/
User login(User user);
}
package com.lut.service;
import com.lut.dao.UserDao;
import com.lut.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User login(User user) {
//接受用户查询数据库
User userDB = userDao.login(user);
//查询到这个用户就返回,没有则抛出错误
if (userDB != null) {
return userDB;
}else{
throw new RuntimeException("登录失败!");
}
}
}
7.8、controller层
package com.lut.controller;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lut.entity.User;
import com.lut.service.UserService;
import com.lut.utils.JWTUtils;
import jdk.nashorn.internal.runtime.CodeInstaller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/test")
public String test(User user) {
return "ok";
}
@GetMapping("/user/login")
public Map<String, Object> login(User user) {
log.info("用户名:[{}]", user.getName());
log.info("密码:[{}]", user.getPassword());
HashMap<String, Object> map = new HashMap<>();
try {
//获取user对象
User userDB = userService.login(user);
//将user对象添加到map
Map<String, String> payload = new HashMap<>();
payload.put("id", userDB.getId().toString());
payload.put("name", userDB.getName());
//生成令牌
String token = JWTUtils.getToken(payload);
//
map.put("state", true);
map.put("msg", "认证成功");
map.put("token", token);
} catch (Exception e) {
map.put("state", false);
map.put("msg", e.getMessage());
}
return map;
}
@PostMapping("/user/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
log.info("当前token为:[{}]", token);
try {
DecodedJWT verify = JWTUtils.verify(token);
map.put("state", true);
map.put("msg", "请求成功");
return map;
} catch (SignatureVerificationException e) {
map.put("msg", "签名不一致异常");
e.printStackTrace();
} catch (TokenExpiredException e) {
map.put("msg", "令牌过期异常");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
map.put("msg", "算法不匹配异常");
e.printStackTrace();
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("state", false);
return map;
}
}
7.9、测试
成功登录:
成功测试:
使用拦截器
原先进行测试的方法存在大量的代码冗余,如果要 对于每个路径都写一遍处理方法是不可能的,对于这一部分我们可以单独的抽取出来,做成一个拦截器,这样可以对不同的路径按照情况进行拦截。
7.10、配置拦截器
确定哪些内容要拦截
package com.lut.config;
import com.lut.interceptors.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test") //其他接口保护
.excludePathPatterns("/user/login");//所有用户接口放行
// //这个才是一般开发中的使用方法,上面那个只是用于测试
// registry.addInterceptor(new JWTInterceptor())
// .addPathPatterns("/**") //其他接口保护
// .excludePathPatterns("/user/**");//所有用户接口放行
}
}
在处理路径之前拦截
package com.lut.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lut.utils.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map=new HashMap<>();
//获取请求头中的令牌
String token=request.getHeader("token");
try {
//验证令牌
JWTUtils.decodedJWT(token);
//放行请求
return true;
} catch (SignatureVerificationException e) {
map.put("msg", "签名不一致异常");
e.printStackTrace();
} catch (TokenExpiredException e) {
map.put("msg", "令牌过期异常");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
map.put("msg", "算法不匹配异常");
e.printStackTrace();
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("state",false);
//将map 转为 json
String json=new ObjectMapper().writeValueAsString(map);
response.setContentType("appliaction/json;charset=UTF-8");
return false;
}
}
7.11、测试结果
最好是将 令牌 封装在 header 中
7.12、项目结构
作者:耿鬼不会笑
时间:2021年1月