一.关于JWT的概念
JWT可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名,于在各方之间作为JSON对象安全地传输信息。
简单理解为就是:将登录当前用户信息进行加密变成一个令牌,用户就可以通过这个令牌在不同的地方进行访问,不需要再次登录。
2.JWT的结构
主要结构由三部分组成:
JWT共由三部分组成,分别是数据头(Header)、Payload(数据体)、验证签名(Verify Signature)组成。其中,Header中的内容为加密信息以及Token的类别,Payload为用户数据、Verify Signature为校验数据。
数据头(Header)
alg:"RS256" ,声明加密的算法
"typ":"JWT" 声明类型
通过Base64进行加密,就可以得到我们token的前一部分签名
Payload(数据体)
sub:"" 可以保存用户信息或者用户id都可以
iat:" " 设置token的过期时间 比如4个小时等 防止被篡改
verify signature(验证签名)
这个意思很明显,就是查看你的token是否被篡改等.
二.JWT的执行流程
1.当用户发起登录请求的时候,验证通过,然后让JWT生成token然后给前端
2.前端获取到token信息的时候,可以放在请求头,当用户需要访问其他接口的时候,每次访问需要携带JWT token。
3.当服务端通过对token的拦截,进行对token的校验以及解析,发现没有没有问题,就开始执行业务逻辑把数据返回给前端,前端在显示给用户
三.开始搭建项目简单使用JWT SpringBoot+Mybatis-plus+Mysql+JWT(实测可用)
下载地址在:
整体架构图
1.引入Mybatis和JWT的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.10</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.配置yml的Mybatis,我的数据库名称为test
server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/tset?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
mybatis-plus.type-aliases-package=com.example.entity
mybatis-plus.configuration.cache-enabled=false
mybatis-plus.global-config.db-config.id-type=auto
//注意修改数据库名称以及数据库密码
3.在数据库中添加一张用户
4.在entity下创建实体类
5.在mapper包下创建
6.在resources下的mapper文件创建
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.SysUserMapper"> //与上面的mapper路径一致 </mapper>
7.在utils下封装工具类
//封装工具类
@Component
public class JWTUtils {
private static String TOKEN ="token"; //自己随便写 但注意保密
/**
* 生成token
* map //传入用户的id和其他信息都可以
*/
public static String getToken(SysUser user){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR,12);
String token= JWT.create()
.withExpiresAt(calendar.getTime()) //token有效期
.withSubject(user.getId().toString()) //保存用户id
.sign(Algorithm.HMAC256(TOKEN));//TOKEN加密密钥自己设置,
return token;
}
/**
* 验证token
* @param token
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
/**
* 通过token获取用户的id
* @param token
* @return
*/
public static Long getToken(String token){
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN)).build();
DecodedJWT jwt = verifier.verify(token);
String jwtSubject = jwt.getSubject();
return Long.valueOf(jwtSubject);
}
}
8在config包下创建一个校验类和拦截器的类
校验类的作用就是,获取到token,然后对token进行验证是否正确,如果过期或者被篡改过,那就需要重新认证登录
拦截器的作用就是 当用户开始发起请求的时候,不会直接访问请求接口返回数据,而是先会token进行拦截,然后去认证,认证通过就让用户正常的访问
public class JWTInterceptor implements HandlerInterceptor { //校验类
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<Object, Object> map = new HashMap<>();
String token = request.getHeader("token");//获取请求头中的令牌
try {
JWTUtils.verify(token); //验证令牌
Long userId = JWTUtils.getToken(token);
return true; //放行请求
}catch (SignatureVerificationException e){
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
}catch (AlgorithmMismatchException e){
e.printStackTrace();
map.put("msg","token算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("state",false);
String value = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(value);
return false;
}
}
@Configuration
public class JWTConfig implements WebMvcConfigurer { //配置拦截器 那些需要token 那些不需要
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**") //上面表示需要有token的验证/user/info就需要token
.excludePathPatterns("/user/login"); //对路径user下面的路径不需要token验证 不需要拦截,就是对controller里面的路径进行拦截或者不拦截
}
}
9.在service下进行登录逻辑代码
@Service
public class SysUserService {
@Autowired
private SysUserMapper userMapper;
public String login(String username){
SysUser user = userMapper.selectOne(new QueryWrapper<SysUser>().eq("user_name",username));
// SysUser user = userMapper.selectById(id);
if (user ==null)return new RuntimeException("该用户未注册").toString();
String token = JWTUtils.getToken(user);
return token;
}
public SysUser getById(Long id){
return userMapper.selectById(id);
}
}
10.controller层
@RestController
@RequestMapping("/user")
public class SysUserController {
@Autowired
private SysUserService userService;
@GetMapping("/login")
public Map<String,String> login(LoginDto dto){ //这里是JWT登录成功把token返回给前端
String token = userService.login(dto.getUsername());
Map<String, String> map = new HashMap<>();
map.put("token",token);
return map;
}
@GetMapping("/info")
public SysUser info(HttpServletRequest request){
Long userId = JWTUtils.getToken(request.getHeader("token"));//通过token获取用户id去查用户信息
SysUser user = userService.getById(userId);
return user;
}
/*
@GetMapping("/code")
public Map<String,String> code(Long id){ //这里是JWT登录成功把token返回给前端
String token = userService.login(id);
Map<String, String> map = new HashMap<>();
map.put("token",token);
return map;
}*/
}
11.postman进行测试