SpringBoot整合JWT

  • 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
  • 座右铭:未来是不可确定的,慢慢来是最快的。
  • 个人主页极客李华-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.引入依赖
<!--引入jwt-->
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.4.0</version>
</dependency>
# 2.生成token

在这里插入图片描述

// 创建一个 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.根据令牌和签名解析数据

在这里插入图片描述

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<String,String> 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

项目结构

在这里插入图片描述

导入依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.2</version>
		</dependency>
		<!--引入jwt-->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.4.0</version>
		</dependency>


		<!--引入mybatis-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.3</version>
		</dependency>

		<!--引入lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.12</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>
			<version>8.0.32</version>
		</dependency>

数据库准备

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;

在这里插入图片描述

后端代码编写

Bean
User
/**
 * <p>
 * 
 * </p>
 *
 * @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<T> {

    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 <T> Result<T> fail(String message) {
        return new Result<>(500, message);
    }

    /**
     * 返回执行失败的结果(自定义状态码和提示信息)
     *
     * @param code    状态码
     * @param message 提示信息
     * @return 失败的结果对象
     */
    public static <T> Result<T> 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<String,String> 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<String, Object> 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
/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author jakelihua
 * @since 2023-08-14
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
    @Select("select * from user where name = #{name} and password = #{password}")
    User login(@Param("name") String name, @Param("password") String password);
}

Service
IUserService
/**
 * <p>
 *  服务类
 * </p>
 *
 * @author jakelihua
 * @since 2023-08-14
 */
public interface IUserService extends IService<User> {
    User login(User user);//登录接口
}
UserServiceImpl
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author jakelihua
 * @since 2023-08-14
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 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
/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author jakelihua
 * @since 2023-08-14
 */
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;

    @GetMapping("/login")
    public Result<Map<String, Object>> login(User user) {
        // 打印用户名和密码
        log.info("用户名: [{}]", user.getName());
        log.info("密码: [{}]", user.getPassword());

        // 创建结果对象
        Result<Map<String, Object>> result;

        try {
            // 调用userService的login方法进行用户认证
            User userDB = userService.login(user);

            // 获取用户ID和用户名,并将其放入payload
            Map<String, String> 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<Map<String, Object>> test(HttpServletRequest request) {
        // 创建结果对象
        Result<Map<String, Object>> result;

        try {
            Map<String, Object> 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;
    }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

# 9.编写测试接口
@PostMapping("/test/test")
public Map<String, Object> test(String token) {
  Map<String, Object> 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;
}

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

课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客李华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值