JWT简介
- JWT是用于微服务之间传递用户信息的一段加密字符串,该字符串是一个JSON格式,各个微服务可以根据该JSON字符串识别用户的身份信息,这个JSON字符串可以封装用户的身份信息
- JWT的构成
- 头部(Header)
- 头部用于描述JWT的基本信息,指定了令牌类型和加密算法
- 载荷(Payload)
- 载荷是存放有效信息的地方
- 标准注册中的声明
- iss: jwt签发者
- sub: 当前令牌的描述说明
- aud: 接收jwt的一方
- exp: jwt的过期时间,过期时间必须大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不能使用的
- iat: jwt的签发时间
- jti: jwt的唯一身份标识
- 公共的声明
- 公共的声明可以添加任何信息,一般添加用户的相关信息或其他业务的必要信息,不建议添加敏感信息,因为该部分在客户端可解密
- 私有的声明
- 私有声明是提供者和消费者所共同定义的声明,不建议存放敏感的信息
- 标准注册中的声明
- 载荷是存放有效信息的地方
- 签名
- jwt的第三部分是一个签证信息,用于校验令牌是否被修改
- 签名信息由以下三个部分组成
- Header(Base64后的)
- Payload(Base64后的)
- Secret(秘钥)
- 签名的组成
- 头部信息Base64 + 载荷Base64 + Header中声明的加密方式进行加盐Secret组合加密,从而构成第三部分,也就是所谓的签名
- 头部(Header)
SpringBoot整合jwt操作
1.创建数据库,建立user_info表,用于用户认证数据
-- 创建数据库
create DATABASE if not exists jwt_test;
-- 使用当前创建的数据库
use jwt_test;
-- 创建表
create table user_info (
id int primary key auto_increment,
age int,
username varchar(20),
password varchar(15),
address varchar(255)
);
-- 插入数据
insert into user_info(age,username,password,address) values (18,'zhangsan','123456','SZ');
2.创建springboot工程,pom依赖如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
3.创建application.yaml文件,添加mybatis配置信息
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jwt_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
#配置mybatis的mapper映射文件位置
mybatis:
mapper-locations: classpath:mappers/*xml
4.创建启动类JwtApplication.java
@SpringBootApplication
@MapperScan(basePackages = "com.zhuang.dao")
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
}
5.创建数据库对应的实体类UserInfo.java
@Data
public class UserInfo {
private Integer id;
private Integer age;
private String username;
private String password;
private String address;
}
6.创建UserInfoDao接口,提供登录认证和获取用户信息方法
public interface UserInfoDao {
/**
* 查询用户数据
*
* @param username 用户名
* @param password 密码
* @return
*/
UserInfo findByUsernameAndPassword(String username, String password);
/**
* 根据ID获取用户数据
*
* @param id
* @return
*/
UserInfo findUserInfoById(Integer id);
}
7.在resources目录下创建mappers目录,并创建对应的UserInfoDao.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">
<!--namespace:给哪个接口配置的映射,写接口的全限定类名-->
<mapper namespace="com.zhuang.dao.UserInfoDao">
<!--
select标签:表示要执行查询语句;
id:给接口里哪个方法配置的,写方法名;
parameterType: 参数类型
resultType:结果集封装类型
-->
<select id="findByUsernameAndPassword" parameterType="string" resultType="com.zhuang.pojo.UserInfo">
select id, age, username, address from user_info where username = #{username} and password = #{password};
</select>
<select id="findUserInfoById" resultType="com.zhuang.pojo.UserInfo">
select id, age, username, address from user_info where id = #{id};
</select>
</mapper>
8.创建对应的service层
//service接口
public interface UserInfoService {
/**
* 查询用户数据
*
* @param username 用户名
* @param password 密码
* @return
*/
UserInfo findByUsernameAndPassword(String username, String password);
/**
* 根据ID获取用户数据
*
* @param id
* @return
*/
UserInfo findUserInfoById(Integer id);
}
//service实现类
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
/**
* 查询用户数据
*
* @param username 用户名
* @param password 密码
* @return
*/
@Override
public UserInfo findByUsernameAndPassword(String username, String password) {
return userInfoDao.findByUsernameAndPassword(username, password);
}
/**
* 根据ID获取用户数据
*
* @param id
* @return
*/
@Override
public UserInfo findUserInfoById(Integer id) {
return userInfoDao.findUserInfoById(id);
}
}
9.创建对应的controller层
@RestController
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 登录
*
* @param userInfo 请求体
* @return
*/
@PostMapping("/user/login")
public Map<String, String> login(@RequestBody UserInfo userInfo) {
UserInfo user = userInfoService.findByUsernameAndPassword(userInfo.getUsername(), userInfo.getPassword());
Map<String, String> map = new HashMap<>();
if (user != null) {
Map<String, String> payload = new HashMap<>();
payload.put("userId", user.getId() + "");
payload.put("username", user.getUsername());
String token = JwtUtil.generateToken(payload);
map.put("status", "OK");
map.put("token", token);
return map;
}
map.put("status", "failed");
return map;
}
/**
* 根据ID获取用户数据
*
* @param id 用户id
* @return
*/
@GetMapping("/user/info/id/{id}")
public UserInfo getUserInfoById(@PathVariable(value = "id") Integer id) {
return userInfoService.findUserInfoById(id);
}
}
10.创建JwtUtil.java
public class JwtUtil {
private static final String SECRET = "CCMetric";
/**
* 生成token
*
* @param payload 载荷,需要在token中存放的数据
* @return
*/
public static String generateToken(Map<String, String> payload) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 1);
JWTCreator.Builder builder = JWT.create();
//载荷,生成token中保存的信息
payload.forEach(builder::withClaim);
return builder.withAudience("admin") //签发对象
.withIssuedAt(new Date()) //发行时间
.withExpiresAt(instance.getTime()) //过期时间
.sign(Algorithm.HMAC256(SECRET)); //加密算法+盐
}
/**
* 校验token,有异常,即为校验失败
*
* @param token token数据
* @return
*/
public static DecodedJWT verify(String token) {
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
}
/**
* 根据token获取载荷信息
*
* @param token token数据
* @return
*/
public static Map<String, Claim> getPayloadByToken(String token) {
return verify(token).getClaims();
}
}
11.创建拦截器JwtInterceptor
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
Map<String, Object> map = new HashMap<>();
if (token != null) {
try {
JwtUtil.verify(token);
return true;
} catch (Exception e) {
map.put("msg", "token无效");
}
} else {
map.put("msg", "token为空");
}
map.put("status", false);
//错误信息响应到前台
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(new Gson().toJson(map));
response.setStatus(HttpStatus.FORBIDDEN.value());
return false;
}
}
12.将拦截器JwtInterceptor添加配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 添加自定义的拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/**") //拦截路径
.excludePathPatterns("/user/login"); //登录接口排除
}
}
13.接口测试
登录测试,返回token
获取用户信息,提供伪造token
获取用于信息,提供正确token