1、Token介绍(摘取自官方网站)
官方网站:JSON Web Token Introduction - jwt.io
1)token可以做权限的验证和信息的交换
2)token主要有三部分组成
分别是Header、Payload、Signature。
编码之后token呈xxxxx.yyyyy.zzzzz结构分别对应Header、Payload、Signature三部分。
3)token示例
2、开始准备demo
目标:
1、使用postman工具模拟发送请求登录
2、登录验证成功后返回token
3、postman模拟前端工程师将token写入请求的header中
4、访问测试方法进行验签
1)准备一个JWTUtil用来封装JWT常用的方法
package com.example.jwtdemo.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
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 SIGNATURE = "!pcfhaHJI#$%";
/**
* 生成 token header.payload.signature
*/
public static String getToken(Map<String, String> map, Integer expireTime) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.MINUTE, expireTime);
JWTCreator.Builder builder = JWT.create();
// payload
map.forEach((k, v)->{
builder.withClaim(k, v);
});
String token = builder.withExpiresAt(instance.getTime()) // 指定令牌过期的时间.
.sign(Algorithm.HMAC256(SIGNATURE));// 签名
return token;
}
/**
* 验证token合法性
* @param token 传入token
*/
public static DecodedJWT verify(String token){
// 验签对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SIGNATURE)).build();
// 验证token
DecodedJWT verify = jwtVerifier.verify(token);
return verify;
}
}
2)创建一个测试数据库
用来存放我们的数据,模拟真实的登录验证。
SQL语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
`user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称',
`login_account` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录账号',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录密码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, '杰克', 'jack', '123123');
SET FOREIGN_KEY_CHECKS = 1;
3)创建mapper和映射文件.xml
由于测试较为简单,所以不进行mybatis的逆向工程
a、首先在yaml文件中进行数据库链接等配置
server:
port: 8888
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jwt_test?characterEncoding=utf-8
username: root
password: 123456
mybatis:
type-aliases-package: com.example.jwtdemo.entities
mapper-locations: classpath:/mybatis/mapper/*.xml
b、在entities中创建PO对象和VO对象
package com.example.jwtdemo.entities.po;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserPO {
// 用户id
private long id;
// 用户姓名
private String userName;
// 用户登录账号
private String loginAccount;
// 用户密码
private String password;
}
package com.example.jwtdemo.entities.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class LoginUserVO {
// 用户登录的账号
private String loginAccount;
// 用户登录的密码
private String password;
}
c、创建mapper接口
@Mapper
package com.example.jwtdemo.mapper;
import com.example.jwtdemo.entities.po.UserPO;
import com.example.jwtdemo.entities.vo.LoginUserVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserPOMapper {
UserPO login(@Param("loginUserVO") LoginUserVO loginUserVO);
}
d、创建xml映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.jwtdemo.mapper.UserPOMapper">
<resultMap id="UserMap" type="com.example.jwtdemo.entities.po.UserPO">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="loginAccount" column="login_account" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
</resultMap>
<select id="login" parameterType="loginUserVO" resultMap="UserMap">
select * from t_user
where login_account = #{loginUserVO.loginAccount} and password = #{loginUserVO.password}
</select>
</mapper>
3、业务层代码
1)service接口
package com.example.jwtdemo.service;
import com.example.jwtdemo.entities.vo.LoginUserVO;
public interface TestJWTService {
// 用户登录
String login(LoginUserVO loginUserVO);
}
2)实现类
package com.example.jwtdemo.service.impl;
import com.example.jwtdemo.entities.po.UserPO;
import com.example.jwtdemo.entities.vo.LoginUserVO;
import com.example.jwtdemo.mapper.UserPOMapper;
import com.example.jwtdemo.service.TestJWTService;
import com.example.jwtdemo.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
@Service
public class TestJWTServiceImpl implements TestJWTService {
@Autowired
private UserPOMapper userPOMapper;
/**
* 用户登录
* @param loginUserVO 表单传递的用户信息
* @return 冲数据库中查到的用户信息
*/
@Override
public String login(LoginUserVO loginUserVO) {
// 查询数据库
UserPO User = userPOMapper.login(loginUserVO);
// 判断是否为空
if (User != null){
// 若返回数据不为空
// 创建HashMao
HashMap<String, String> map = new HashMap<>();
// 放入数据
map.put("id", User.getId() + "");
map.put("userName", User.getUserName());
map.put("loginAcconut", User.getLoginAccount());
return JWTUtils.getToken(map, 2);
}
throw new RuntimeException("数据库中未查到此用户");
}
}
3)控制层代码
testInterceptors 用来测试登录成功之后再次访问后header是否带有token。
具体测试方法在拦截器中(下方)。
@RestController
public class TestJWTController {
@Autowired
private TestJWTService testJWTService;
// 测试拦截器
@GetMapping(value = "/test/interceptors")
public CommonResult<String> testInterceptors(){
return new CommonResult<>(200, "测试成功");
}
// 测试登录
@PostMapping(value = "/test/user/login")
public CommonResult<String> login(LoginUserVO loginUserVO){
try {
// 成功则直接返回
String token = testJWTService.login(loginUserVO);
return new CommonResult<>(200, "登录成功", token);
} catch (Exception exception) {
// 不成功则返回异常
exception.printStackTrace();
return new CommonResult<>(444, exception.getMessage());
}
}
}
4、WebMvcConfig
1)创建拦截器
前后端分离项目一般将token放在header中,后端通过拦截器在请求达到之前拿到header中的token并进行验签,验签成功则放行,失败则拦截。
package com.example.jwtdemo.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.jwtdemo.controller.TestJWTController;
import com.example.jwtdemo.entities.CommonResult;
import com.example.jwtdemo.utils.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author : pcf
* @date : 12:15 2021/10/23
*/
@Slf4j
public class TestTokenInterceptor implements HandlerInterceptor {
// 请求之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 拿到header中的token
String token = request.getHeader("token");
// 创建返回消息体
CommonResult<String> result = null;
// 对token进行验签
try {
// 验签成功则放行
JWTUtils.verify(token);
return true;
} catch (TokenExpiredException exception) {
// token过期
exception.printStackTrace();
result = new CommonResult<>(400, "token过期");
} catch (SignatureVerificationException exception) {
// 签名错误
exception.printStackTrace();
result = new CommonResult<>(400, "签名错误");
} catch (AlgorithmMismatchException exception) {
// 加密算法不匹配
exception.printStackTrace();
result = new CommonResult<>(400, "加密算法不匹配");
} catch (Exception exception) {
// 无效token
exception.printStackTrace();
result = new CommonResult<>(400, "无效token");
}
// 将返回消息体转化为json
String json = new ObjectMapper().writeValueAsString(result);
// 设置编码类型
response.setContentType("application/json;charset=UTF-8");
// 放入响应中
response.getWriter().println(json);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2)在WebConfig中注册拦截器
为了测试简单,我们仅仅测试 http://localhost:8888/test/interceptors 这一个请求来达到我们测试token的目的。
package com.example.jwtdemo.config;
import com.example.jwtdemo.interceptors.TestTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author : pcf
* @date : 11:04 2021/10/23
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestTokenInterceptor()).addPathPatterns("/test/interceptors");
}
}
至此我们的demo搭建完毕下面进行测试。
5、测试
我们使用postman作为测试工具进行测试
1)测试登录
发送post请求,模拟表单登录!
登录成功后后端返回token
2)测试登录之后请求的header中是否带有token
将token写入header
验签成功!
idea后台打印
3)修改token
随便修改token中的字母或数字。 (模拟解码后修改数据并再次编码)
控制台报错,token验签失败!