1、基本认识
jwt : JSON Web Token, json形式的web令牌 。常用于前后端分离项目的安全验证。
jwt应用场景:
1、一次性验证:比如邮箱注册,返回给你的连接可能就是jwt
2、restful api 的无状态认证,可以代替session
jwt的token由三部分构成,将这三段信息文本用.链接一起就构成了Jwt字符串。
如下例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
这三部分分别叫做:header、playload、signature
- header : 声明类型、加密算法
- playload :存放有效信息,如签发者、用户、签发时间等
- signature : 由前两部分组合然后加盐加密构成
2、生成token、解析
1、依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2、生成token
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,200); // 200秒
String token = JWT.create()
.withClaim("userid", 12) // payload
.withClaim("username", "zhang")
.withExpiresAt(instance.getTime()) // 设置过期时间,200秒
.sign(Algorithm.HMAC256("123ABCD!")); // 密钥
System.out.println(token);
如上代码输出内容
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTE2Mjc0ODksInVzZXJpZCI6MTIsInVzZXJuYW1lIjoiemhhbmcifQ.JeEaSypQrT0Dxtix4ljS1LSvjdYoF8Q0zZ9_y3yajBA
3、根据令牌和签名解析数据
// 创建验证对象
JWTVerifier jwt_v = JWT.require(Algorithm.HMAC256("123ABCD!")).build();
// 根据密钥获取 存有token数据的对象
DecodedJWT verify = jwt_v.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTE2Mjc0ODksInVzZXJpZCI6MTIsInVzZXJuYW1lIjoiemhhbmcifQ.JeEaSypQrT0Dxtix4ljS1LSvjdYoF8Q0zZ9_y3yajBA");
// 打印数据
System.out.println(verify.getClaim("userid").asInt());
System.out.println(verify.getClaim("username").asString());
// 打印过期时间
System.out.println(verify.getExpiresAt());
执行 根据令牌和签名解析数据 时如果出现以下内容表示令牌过期了
下面是各种异常对应的解释
3、与spring boot整合使用
3.1、准备使用场景(创建项目)
创建一个springboot项目
下面是要用到的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</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>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
配置文件 application.properties
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?characterEncoding=UTF-8
spring.datasource.password=root
spring.datasource.username=root
mybatis.type-aliases-package=com.beaninj.entity
mybatis.mapper-locations=classpath:com/beaninj/mapper/*.xml
logging.level.com.beaninj.dao=debug
新建数据库jwt、表user
CREATE DATABASE jwt
use jwt
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL,
`name` varchar(255) DEFAULT NULL,
`pwd` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `user` VALUES ('1', 'zhang', '123');
下面是接下来要新建的内容的目录结构
新建实体类User.java
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class User {
private int id;
private String name;
private String pwd;
}
新建UserDao.java
import com.beaninj.springboot_jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao {
User login(User user);
}
新建mapper.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.beaninj.springboot_jwt.dao.UserDao">
<select id="login" parameterType="com.beaninj.springboot_jwt.entity.User" resultType="com.beaninj.springboot_jwt.entity.User">
select * from user where name=#{name} and pwd=#{pwd}
</select>
</mapper>
新建UserService.java
import com.beaninj.springboot_jwt.entity.User;
public interface UserService {
User login(User user);
}
新建UserServiceImpl.java
import com.beaninj.springboot_jwt.dao.UserDao;
import com.beaninj.springboot_jwt.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User user1 = userDao.login(user);
if(user1!=null) return user1;
throw new RuntimeException("登录失败");
}
}
新建UserController.java
import com.beaninj.springboot_jwt.entity.User;
import com.beaninj.springboot_jwt.service.UserService;
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.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("login")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getName());
log.info("密码:[{}]",user.getPwd());
Map<String,Object> map=new HashMap<>();
try {
User user1 = userService.login(user);
map.put("state","true");
map.put("msg","认证成功");
} catch (Exception e) {
map.put("state","false");
map.put("msg",e.getMessage());
}
return map;
}
}
运行、测试http://localhost:8080/login?name=zhang&pwd=123
3.2、引入jwt
第一步:加入jwt对应依赖,及其他可能用到的依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
第二步:封装一个jwt的token生成、验证的工具类。
新建工具类JWTUtils.java
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 {
// 生成环境下这个SING是非常严格的,要保密,不能让别人知道
private static final String SING = "xu!It'sASecret";
/*
* 生成token
* @返回值 token
**/
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); // 过期时间7天
// 创建builder
JWTCreator.Builder builder = JWT.create();
// 将数据遍历放进去 下面那个代码也可以写成这个样 map.forEach((k,v)->{builder.withClaim(k,v);});
map.forEach(builder::withClaim);
// 设置过期时间7天
builder.withExpiresAt(instance.getTime());
// 设置密钥,并返回
return builder.sign(Algorithm.HMAC256(SING));
}
/*
* 验证token
**/
public static void verify(String token) {
JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
/*
* 获取token信息
**/
public static DecodedJWT getTokenInfo(String token) {
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}
接下来在controller里面使用这个工具类
生成token方法的使用
验证token
UserController.java到目前完整代码
import com.auth0.jwt.interfaces.DecodedJWT;
import com.beaninj.springboot_jwt.entity.User;
import com.beaninj.springboot_jwt.service.UserService;
import com.beaninj.springboot_jwt.util.JWTUtils;
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.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("login")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getName());
log.info("密码:[{}]",user.getPwd());
Map<String,Object> map=new HashMap<>();
try {
User user1 = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("id",user1.getId()+"");
payload.put("name",user1.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("test")
public Map<String,Object> tset(String token){
Map<String,Object> map=new HashMap<>();
log.info("当前token为:[{}]",token);
try {
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
map.put("state","true");
map.put("msg","请求成功");
return map;
} catch (Exception e) {
// 这里面抛出与JWT相关的异常有:令牌过期、签名不一致、算法不匹配、token无效等
e.printStackTrace();
}
map.put("state","false");
map.put("msg","认证失败");
return map;
}
}
运行项目,测试
先生成token
再验证token
3.3、利拦截器方式结合jwt
到目前为止,我们发现如果每个接口都去添加jwt相关验证代码,会很冗余,于是我们用拦截器的方式进行jwt验证。
新建接口包、类JWTInterceptors.java
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.beaninj.springboot_jwt.util.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/*
* jwt拦截器
* 实现spring提供的拦截器接口
**/
public class JWTInterceptors implements HandlerInterceptor {
// 实现preHandle方法(预先拦截方法)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 直接在http请求头中拿token
String token = request.getHeader("token");
// 用来传递 请求后 返回的消息
Map<String,Object> map=new HashMap<>();
// 验证令牌
try {
JWTUtils.getTokenInfo(token);
return true;
}catch (SignatureVerificationException e) {
map.put("msg","签名无效");
}catch (TokenExpiredException e) {
map.put("msg","token过期");
} catch (AlgorithmMismatchException e) {
map.put("msg","token算法不一致");
}catch (Exception e) {
e.printStackTrace();
map.put("msg","其他异常");
}
map.put("state","false");
// 将map转为json
String json = new ObjectMapper().writeValueAsString(map);
// 给前端响应转为json后的map
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
新建一个InterceptorConfig.java (用来将拦截器配置进spring)
import com.beaninj.springboot_jwt.interceptors.JWTInterceptors;
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 JWTInterceptors())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/user/**"); // 排除拦截user下面的路径
}
}
UserController里面就不用验证token了
下面是修改后的代码
import com.auth0.jwt.interfaces.DecodedJWT;
import com.beaninj.springboot_jwt.entity.User;
import com.beaninj.springboot_jwt.service.UserService;
import com.beaninj.springboot_jwt.util.JWTUtils;
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 javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/login")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getName());
log.info("密码:[{}]",user.getPwd());
Map<String,Object> map=new HashMap<>();
try {
User user1 = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("id",user1.getId()+"");
payload.put("name",user1.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("test")
public Map<String,Object> tset(){
// 这里面就不用验证token了,因为拦截器已经在这之前已经处理了
Map<String,Object> map=new HashMap<>();
map.put("state","true");
map.put("msg","请求成功");
return map;
}
@PostMapping("test1")
public Map<String,Object> tset1(HttpServletRequest request){
// 如果需要获取token里面的信息,这里也可以拿到
String token = request.getHeader("token");
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
System.out.println(tokenInfo.getClaim("id").asString());
System.out.println(tokenInfo.getClaim("name").asString());
Map<String,Object> map=new HashMap<>();
map.put("state","true");
map.put("msg","请求成功");
return map;
}
}