相关推荐recommended
SpringBoot整合JWT
作者:mmseoamin日期:2024-04-27
  • 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
  • 座右铭:未来是不可确定的,慢慢来是最快的。
  • 个人主页:极客李华-CSDN博客
  • 合作方式:私聊+
  • 这个专栏内容:BAT等大厂常见后端java开发面试题详细讲解,更新数目100道常见大厂java后端开发面试题。
  • 我的CSDN社区:https://bbs.csdn.net/forums/99eb3042821a4432868bb5bfc4d513a8
  • 微信公众号,抖音,b站等平台统一叫做:极客李华,加入微信公众号领取各种编程资料,加入抖音,b站学习面试技巧,职业规划

SpringBoot整合JWT

如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下

引言:

在当今的互联网时代,身份验证和授权是保护应用程序和保护用户数据的关键。而 JSON Web Token (简称 JWT)是一种用于身份验证和授权的开放标准,广泛应用于web应用程序和API中。本文将深入介绍 JWT,包括其组成、工作原理以及常见的应用场景。

1. 什么是 JSON Web Token (JWT)?

JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式来在各方之间安全地传输信息。它是一个基于 JSON 格式的令牌,由三个部分组成:头部(Header)、载荷(Payload)、签名(Signature)。其中,每一部分都使用 Base64 编码,形成一个使用点进行分隔的字符串。

2. JWT 的组成

  • 头部(Header):头部通常由两部分组成,即令牌的类型(通常使用 “JWT”)和所使用的算法(如 HMAC SHA256 或 RSA)。
  • 载荷(Payload):载荷包含有关声明或实体的声明。载荷可以包含例如用户名、用户ID、角色等相关信息。此外,JWT 还可以包含其他自定义的声明。
  • 签名(Signature):签名部分用于验证令牌的真实性,并确保它未被篡改。签名是通过将头部、载荷和一个秘密密钥进行加密生成的。

    3. JWT 的工作原理

    JWT 工作原理如下:

    • 客户端通过身份验证成功后,服务器将生成一个 JWT。
    • 服务器将 JWT 发送给客户端,并存储在客户端(通常是在 Cookie 或本地存储中)。
    • 客户端在每次请求时将 JWT 添加到请求的头部或参数中。
    • 服务器接收到请求后使用相同的密钥来验证 JWT 的真实性和完整性。
    • 如果验证成功,服务器根据 JWT 中的声明完成对用户的身份验证和授权操作。

      4. JWT 的应用场景

      JWT 是一种灵活而强大的工具,可用于多种应用场景,包括:

      • 用户认证:通过将用户信息存储在 JWT 中,实现用户身份验证和提供访问权限。
      • 单点登录:当用户在不同的应用程序之间切换时,只需使用 JWT 进行一次身份验证即可访问多个应用程序。
      • API 授权:通过在每个请求中添加 JWT,可以轻松地实现对 API 的授权访问,从而提高安全性。

        引入JWT

        # 1.引入依赖
        
        
        
          com.auth0
          java-jwt
          3.4.0
        
        
        # 2.生成token
        

        SpringBoot整合JWT,在这里插入图片描述,第1张

        // 创建一个 Calendar 实例,用于设置过期时间
        Calendar instance = Calendar.getInstance();
        // 在当前时间的基础上增加90秒
        instance.add(Calendar.SECOND, 90);
        // 生成令牌
        String token = JWT.create()
            .withClaim("username", "张三") // 设置自定义用户名
            .withExpiresAt(instance.getTime()) // 设置过期时间为 Calendar 实例的时间
            .sign(Algorithm.HMAC256("token!Q2W#E$RW")); // 使用 HMAC256 签名算法和密钥进行签名
        // 输出令牌
        System.out.println(token);
        

        注释解释:

        1. 创建一个 Calendar 实例,表示当前时间,该实例用于设置过期时间。

        2. 将实例的时间增加90秒,作为令牌的过期时间。

        3. 使用 JWT.create() 方法创建一个 JWT 实例,用于生成令牌。

        4. 使用 withClaim("username", "张三") 方法设置自定义声明,在这里设置了用户名。

        5. 使用 withExpiresAt(instance.getTime()) 方法设置过期时间,将 instance 实例的时间设置为令牌的过期时间。

        6. 使用 sign(Algorithm.HMAC256("token!Q2W#E$RW")) 方法进行签名处理。使用 HMAC256 算法,并提供密钥进行签名。密钥字符串 "token!Q2W#E$RW" 在真正的系统中应该是保密且足够复杂的。

        7. 将生成的令牌存储在 token 变量中。

        8. 使用 System.out.println(token) 将令牌内容输出到控制台。

        - 生成结果
        eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
        
        # 3.根据令牌和签名解析数据
        

        SpringBoot整合JWT,在这里插入图片描述,第2张

        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
        System.out.println("过期时间: "+decodedJWT.getExpiresAt());
        
        ## 常见异常信息
        - SignatureVerificationException:				签名不一致异常
        - TokenExpiredException:    						令牌过期异常
        - AlgorithmMismatchException:						算法不匹配异常
        - InvalidClaimException:								失效的payload异常
        

        封装工具类

        /**
         * JWTUtils 类用于生成和验证 JWT 令牌,以及获取令牌中的 payload。
         */
        public class JWTUtils {
            private static String TOKEN = "token!Q@W3e4r"; // 定义密钥
            /**
             * 生成 JWT 令牌
             * @param map 传入的 Payload 数据
             * @return 返回生成的令牌
             */
            public static String getToken(Map map){
                JWTCreator.Builder builder = JWT.create();
                // 遍历传入的 Payload 数据,并添加到 Builder 中
                map.forEach((k,v)->{
                    builder.withClaim(k,v);
                });
                Calendar instance = Calendar.getInstance();
                instance.add(Calendar.SECOND,7);
                // 设置过期时间为 7 秒后
                builder.withExpiresAt(instance.getTime());
                // 使用 HMAC256 签名算法进行签名,并返回令牌字符串
                return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
            }
            /**
             * 验证 JWT 令牌
             * @param token 要验证的令牌字符串
             */
            public static void verify(String token){
                // 创建一个 JWTVerifier 实例,使用相同的密钥构建
                JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
            }
            /**
             * 获取令牌中的 Payload 数据
             * @param token 要解析的令牌字符串
             * @return 解码后的令牌对象(DecodedJWT)
             */
            public static DecodedJWT getToken(String token){
                // 创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码
                return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
            }
        }
        

        注释解释:

        1. TOKEN 是用来生成和验证 JWT 令牌所使用的密钥。
        2. getToken() 方法用于生成 JWT 令牌,接收一个 Map 类型的参数作为 Payload 数据,并返回生成的令牌字符串。
        3. 遍历传入的 Payload 数据,将每个键值对添加到 JWT 的 Builder 实例中。
        4. 获取当前时间,并在此基础上增加7秒,作为令牌的过期时间。
        5. 使用 Builder 设置令牌的过期时间。
        6. 使用 HMAC256 签名算法和密钥对令牌进行签名,并将其转换成字符串返回。
        7. verify() 方法用于验证 JWT 令牌,接收令牌字符串作为参数。创建一个 JWTVerifier 实例,使用相同的密钥进行构建,并对令牌进行验证。
        8. getToken() 方法用于获取令牌中的 Payload 数据。接收要解析的令牌字符串作为参数。创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码。返回解码后的令牌对象(DecodedJWT)。

        整合springboot

        项目结构

        SpringBoot整合JWT,在这里插入图片描述,第3张

        导入依赖

        		
        			org.springframework.boot
        			spring-boot-starter-web
        		
        		
        			com.baomidou
        			mybatis-plus-boot-starter
        			3.4.2
        		
        		
        			org.springframework.boot
        			spring-boot-starter-test
        			test
        			
        				
        					org.junit.vintage
        					junit-vintage-engine
        				
        			
        		
        		
        			com.baomidou
        			mybatis-plus-boot-starter
        			3.4.2
        		
        		
        		
        			com.auth0
        			java-jwt
        			3.4.0
        		
        		
        		
        			org.mybatis.spring.boot
        			mybatis-spring-boot-starter
        			2.1.3
        		
        		
        		
        			org.projectlombok
        			lombok
        			1.18.12
        		
        		
        		
        			com.alibaba
        			druid
        			1.1.19
        		
        		
        		
        			mysql
        			mysql-connector-java
        			8.0.32
        		
        

        数据库准备

        DROP TABLE IF EXISTS `user`;
        CREATE TABLE `user` (
          `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
          `name` varchar(80) DEFAULT NULL COMMENT '用户名',
          `password` varchar(40) DEFAULT NULL COMMENT '用户密码',
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
        

        SpringBoot整合JWT,在这里插入图片描述,第4张

        后端代码编写

        Bean
        User
        /**
         * 

        * *

        * * @author jakelihua * @since 2023-08-14 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("user") public class User implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * 用户名 */ private String name; /** * 用户密码 */ private String password; }
        Config
        InterceptorConfig
        /**
         * InterceptorConfig 是一个配置类,用于添加拦截器。
         * 在这个类中,我们可以配置需要拦截的接口路径以及排除不需要拦截的接口路径。
         * 在这个例子中,我们添加了JWTInterceptor拦截器来对请求进行token验证,
         * 并设置了"/user/test"接口需要进行验证,而"/user/login"接口则被排除在验证之外,即所有用户都放行登录接口。
         */
        @Configuration
        public class InterceptorConfig implements WebMvcConfigurer {
            /**
             * 添加拦截器配置
             */
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new JWTInterceptor())
                        .addPathPatterns("/user/test")         // 对"/user/test"接口进行token验证
                        .excludePathPatterns("/user/login");  // 所有用户都放行登录接口
            }
        }
        
        Result
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public class Result {
            private int code;
            private String message;
            private T data;
            public Result(T data) {
                this.code = 200;
                this.message = "success";
                this.data = data;
            }
            public Result(T data, boolean success, String message) {
                if (success) {
                    this.code = 200;
                    this.message = "success";
                } else {
                    this.code = 500; // 自定义错误状态码(示例为500)
                    this.message = message;
                }
                this.data = data;
            }
            public Result(int code, String message) {
                this.code = code;
                this.message = message;
                this.data = null;
            }
            /**
             * 返回执行失败的结果(默认状态码为500)
             *
             * @param message 提示信息
             * @return 失败的结果对象
             */
            public static  Result fail(String message) {
                return new Result<>(500, message);
            }
            /**
             * 返回执行失败的结果(自定义状态码和提示信息)
             *
             * @param code    状态码
             * @param message 提示信息
             * @return 失败的结果对象
             */
            public static  Result fail(int code, String message) {
                return new Result<>(code, message);
            }
        }
        
        Utils
        JWTUtils
        public class JWTUtils {
            private static final String  SING = "!Q@W3e4r%T^Y";
            /**
             * 生成token  header.payload.sing
             */
            public static String getToken(Map map){
                Calendar instance = Calendar.getInstance();
                instance.add(Calendar.DATE,7);//默认7天过期
                //创建jwt builder
                JWTCreator.Builder builder = JWT.create();
                //payload
                map.forEach((k,v)->{
                    builder.withClaim(k,v);
                });
                String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
                        .sign(Algorithm.HMAC256(SING));//sign
                return token;
            }
            /**
             * 验证token 合法性
             *
             */
            public static DecodedJWT verify(String token){
                return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
            }
        //    /**
        //     * 获取token信息方法
        //     */
        //    public static DecodedJWT getTokenInfo(String token){
        //        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
        //        return verify;
        //    }
        }
        
        Interceptor
        JWTInterceptor
        /**
         * JWTInterceptor是一个拦截器,用于验证请求头中的JWT令牌是否有效。
         * 当有请求进入时,该拦截器会首先从请求头中获取令牌,并尝试验证其有效性。
         * 如果令牌验证成功,则放行请求;否则,拦截请求并返回相应的错误信息。
         */
        public class JWTInterceptor implements HandlerInterceptor {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                // 创建一个Map对象,用于存储响应信息
                Map map = new HashMap<>();
                // 从请求头中获取令牌
                String token = request.getHeader("token");
                try {
                    JWTUtils.verify(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); // 设置状态为false
                // 将Map转化为JSON字符串(使用Jackson库)
                String json = new ObjectMapper().writeValueAsString(map);
                response.setContentType("application/json;charset=UTF-8"); // 设置响应的Content-Type
                response.getWriter().println(json); // 将JSON字符串写入响应中
                return false; // 不放行请求
            }
        }
        
        Mapper
        UserMapper
        /**
         * 

        * Mapper 接口 *

        * * @author jakelihua * @since 2023-08-14 */ @Mapper public interface UserMapper extends BaseMapper { @Select("select * from user where name = #{name} and password = #{password}") User login(@Param("name") String name, @Param("password") String password); }
        Service
        IUserService
        /**
         * 

        * 服务类 *

        * * @author jakelihua * @since 2023-08-14 */ public interface IUserService extends IService { User login(User user);//登录接口 }
        UserServiceImpl
        /**
         * 

        * 服务实现类 *

        * * @author jakelihua * @since 2023-08-14 */ @Service public class UserServiceImpl extends ServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Override public User login(User user) { User userDB = userMapper.login(user.getName(), user.getPassword()); System.out.println(userDB); if (userDB != null){ return userDB; } throw new RuntimeException("登录失败~~"); } }
        Controller
        UserController
        /**
         * 

        * 前端控制器 *

        * * @author jakelihua * @since 2023-08-14 */ @Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; @GetMapping("/login") public Result> login(User user) { // 打印用户名和密码 log.info("用户名: [{}]", user.getName()); log.info("密码: [{}]", user.getPassword()); // 创建结果对象 Result> result; try { // 调用userService的login方法进行用户认证 User userDB = userService.login(user); // 获取用户ID和用户名,并将其放入payload Map payload = new HashMap<>(); payload.put("id", userDB.getId().toString()); payload.put("name", userDB.getName()); // 生成JWT的令牌 String token = JWTUtils.getToken(payload); // 构造成功的结果对象 result = new Result<>(200, "认证成功"); result.setData(new HashMap<>()); result.getData().put("token", token); // 响应token } catch (Exception e) { // 构造失败的结果对象 result = Result.fail(500, e.getMessage()); } return result; } @PostMapping("/test") public Result> test(HttpServletRequest request) { // 创建结果对象 Result> result; try { Map map = new HashMap<>(); // 处理自己的业务逻辑 // 从请求头中获取token String token = request.getHeader("token"); // 校验并解析token DecodedJWT verify = JWTUtils.verify(token); // 打印解析出的用户id和用户名 log.info("用户id: [{}]", verify.getClaim("id").asString()); log.info("用户name: [{}]", verify.getClaim("name").asString()); // 构造成功的结果对象 result = new Result<>(200, "请求成功!"); result.setData(map); } catch (Exception e) { // 构造失败的结果对象 result = Result.fail(500, e.getMessage()); } return result; } }

        SpringBoot整合JWT,在这里插入图片描述,第5张

        SpringBoot整合JWT,在这里插入图片描述,第6张

        SpringBoot整合JWT,在这里插入图片描述,第7张

        # 9.编写测试接口
        
        @PostMapping("/test/test")
        public Map test(String token) {
          Map map = new HashMap<>();
          try {
            JWTUtils.verify(token);
            map.put("msg", "验证通过~~~");
            map.put("state", true);
          } catch (TokenExpiredException e) {
            map.put("state", false);
            map.put("msg", "Token已经过期!!!");
          } catch (SignatureVerificationException e){
            map.put("state", false);
            map.put("msg", "签名错误!!!");
          } catch (AlgorithmMismatchException e){
            map.put("state", false);
            map.put("msg", "加密算法不匹配!!!");
          } catch (Exception e) {
            e.printStackTrace();
            map.put("state", false);
            map.put("msg", "无效token~~");
          }
          return map;
        }
        

        SpringBoot整合JWT,在这里插入图片描述,第8张

        SpringBoot整合JWT,在这里插入图片描述,第9张

        SpringBoot整合JWT,在这里插入图片描述,第10张

        如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下