JWT可以理解为一个加密的字符串,里面由三部分组成:头部(Header)、负载(Payload)、签名(signature)
由base64加密后的header和payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT字符串
往期介绍了JWT相关概念以及基本操作,接下来介绍如何在SpringBoot中整合JWT实现登陆注册

环境搭建
1、新建一个SpringBoot项目Jwt-Demo,引入项目后面需要用到的jar包
- <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.1.3</version>
- </dependency>
- <!--引入mysql-->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.25</version>
- </dependency>
- <!--引入druid数据库连接池-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- <version>1.2.1</version>
- </dependency>
- <!--引入lombok-->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.12</version>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter-test</artifactId>
- <version>2.1.3</version>
- </dependency>
- <!--引入jwt-->
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.4.0</version>
- </dependency>
- </dependencies>
2、数据库结构

有一个JWT库,里面还有一个User表
3、配置文件application.properties
- server.port=8989
-
- spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- spring.datasource.url=jdbc:mysql://localhost:3306/JWT?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
- spring.datasource.username=root
- spring.datasource.password=12345678
-
- #mybatis扫描的包
- mybatis.type-aliases-package=com.ylc
- #mapper文件路径
- mybatis.mapper-locations=classpath:/**/*.xml
-
- #开启sql打印日志 logging.level后面是mybatis对应的方法接口所在的包
- logging.level.com.ylc.jwtdemo.dao=debug
4、Entity包下新建一个User类
- import lombok.Data;
-
- @Data
- public class User {
- private String username;
- private String password;
- private int id;
- }
5、Dao包下新建一个UserDao
- @Mapper
- public interface UserDao {
-
- User login(User user);
- }
6、Service包下新建一个USerService
- public interface UserService {
- User login(User user);//登录接口
- }
7、UseService的实现类UserServiceImp
- import java.util.HashMap;
- import java.util.Map;
-
- @Service
- public class UserServiceImpI implements UserService {
-
- @Autowired
- private UserDao userDao;
-
- @Override
- public User login(User user) {
- User userdb=userDao.login(user);
- if(userdb!=null)
- {
- Map<String,String> map=new HashMap<>();
- map.put("name",userdb.getUsername());
- return userdb;
- }
- throw new RuntimeException("登录失败");
- }
- }
8、controller包下新建一个UserController
- @RestController
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @GetMapping("/user/login")
- public Map<String,Object> login(User user)
- {
- log.info("用户名:"+user.getUsername());
- log.info("密码:"+user.getPassword());
- Map<String,Object> map=new HashMap<>();
- try {
- userService.login(user);
- map.put("msg","登录成功");
- map.put("code","200");
- }
- catch (Exception ex)
- {
- map.put("msg","登录失败");
- }
-
- return map;
- }
- }
9、在resource文件夹下新建一个Usermapper文件
- <?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.ylc.jwtdemo.dao.UserDao">
- <select id="login" parameterType="com.ylc.jwtdemo.entity.User" resultType="com.ylc.jwtdemo.entity.User">
- select *from user where username=#{username} and password=#{password}
- </select>
- </mapper>
10、JWT工具类JwtUtils
- /**
- * JWT工具类
- * @author yanglingcong
- * @date 2021/12/31 11:24 AM
- */
- public class JwtUtils {
- //鉴权 相当于私钥保存在服务器上
- private static final String secret="##@$%@#S#WS";
-
-
- /**
- * 生成token
- * @author yanglingcong
- * @date 2021/12/31 11:23 AM
- * @param map
- * @return String
- */
- public static String getToken(Map<String,String> map)
- {
- Calendar instance=Calendar.getInstance();
- //默认七天过期
- instance.add(Calendar.DATE,7);
- //创建JWT
- JWTCreator.Builder builder = JWT.create();
-
- //payload
- map.forEach((k,v)->{
- builder.withClaim(k,v);
- });
- //指定令牌过期时间
- builder.withExpiresAt(instance.getTime());
-
- String token=builder.sign(Algorithm.HMAC256(secret));
- return token;
- }
-
- /**
- * 验证token
- * @author yanglingcong
- * @date 2021/12/31 11:26 AM
- * @param token
- */
- public static DecodedJWT verify(String token) {
- return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
- }
-
- }
整个项目概览

测试验证是否能够连通数据库
访问:localhost:8989/user/login?username=ylc&password=123456

引入JWT
- @Slf4j
- @RestController
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @GetMapping("/user/login")
- public Map<String,Object> login(User user)
- {
- log.info("用户名:"+user.getUsername());
- log.info("密码:"+user.getPassword());
- Map<String,Object> map=new HashMap<>();
- try {
- userService.login(user);
- map.put("msg","登录成功");
- map.put("code","200");
-
- Map<String,String> payload=new HashMap<>();
- payload.put("name",user.getUsername());
- String token= JwtUtils.getToken(payload);
- map.put("token",token);
- }
- catch (Exception ex)
- {
- map.put("msg","登录失败");
- }
-
- return map;
- }
-
- @PostMapping("/test/verity")
- public Map<String,String> verityToken(String token)
- {
- Map<String, String> map=new HashMap<>();
- log.info("token为"+token);
- try {
- DecodedJWT verify = JwtUtils.verify(token);
- map.put("msg","验证成功");
- map.put("state","true");
- }
- catch (Exception exception)
- {
- map.put("msg","验证失败");
- exception.printStackTrace();
- }
- return map;
- }
- }
登录操作
访问:http://localhost:8989/user/login?username=ylc&password=123456

验证操作
访问:http://localhost:8989/test/verity

但是我们这样写在实际项目中是不合理的,把token生成的代码放在了Controller中,业务逻辑是不能放在Controller层中的。假如很多接口都需要token来进行验证保护,那每一个接口都需要添加这样一段代码,造成代码冗余。
程序优化
如果是web项目使用拦截器进行优化,如果是springcloud项目在网关层进行拦截,下面演示如何使用拦截器拦截
最好还把JWT生成token放在http请求头,这样就不需要把token当成参数传递了
新建一个拦截器JwtInterceptor
- /**
- * JWT拦截器
- * @author yanglingcong
- * @date 2021/12/31 12:39 PM
- */
- public class JwtInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- HashMap<String, String> map=new HashMap<>();
- //从http请求头获取token
- String token = request.getHeader("token");
- try {
- //如果验证成功放行请求
- DecodedJWT verify = JwtUtils.verify(token);
- return true;
- }
- catch (Exception exception)
- {
- map.put("msg","验证失败:"+exception);
- }
- String json = new ObjectMapper().writeValueAsString(map);
- response.setContentType("application/json:charset=UTF=8");
- response.getWriter().println(json);
- return false;
- }
- }
然后把拦截器注册到过滤器中,新建一个过滤器InterceptConfig
- /**
- * @author yanglingcong
- */
- @Configuration
- public class InterceptConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //添加拦截器
- registry.addInterceptor(new JwtInterceptor())
- //拦截的路径 需要进行token验证的路径
- .addPathPatterns("/test/verity")
- //放行的路径
- .excludePathPatterns("/user/login");
- }
- }
登录是不需要拦截的,其他请求如果有需要验证token就放入拦截器的路径
测试验证
在http请求头中放入token,会被拦截器拦截验证token的有效性

总结
这就是SpringBoot整合JWT实际项目一个大概的流程,但是细节方面secret(私钥)肯定每个用户都是不一样的,这里给写死了,而且私钥得保存在一个安全的地方。包括payload部分不能存放敏感的密码信息等等,还可以进行优化。
项目代码:https://gitee.com/yanglingcong/jwt-demo
到此这篇关于实战SpringBoot集成JWT实现token验证的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持w3xue。