springbot,JWT令牌的使用。实现http请求拦截校验。

JWT 由三部分组成,用点(.)分隔 Header(头部) Payload(负载)Signature(签名)

一、原理

Jwt原理其实很简单,在后端首先要有个拦截器,他会拦截所有http请求,意思就是所有访问都被拦截掉,当然这是不合理的。所以首先我们要手动放行登陆页面,也就是登录请求不拦截可以访问,然后自此以后的所有请求都会被拦截。如何不让他拦截呢?这时候就要设置一些规则来放行请求,那么这个规则就是Jwt令牌的用处。他会生成一个token在前后端传来传去,怎么传呢?前端的http请求都有一个header,token就会被携带在里面,后端解析http请求,拿到token来验证。如果通过则放行,不通过就拦截。注意(token在实体类中有字段,但是数据库中不必有专门的列来保存token)

  @TableField(exist = false)
     private String token;

二、第一次请求生成token,不验证token

1.首先,当用户成功登陆时,会请求/login

 @PostMapping("/login")
    public Result login(@RequestBody User user) {
        if(StrUtil.isBlank(user.getUsername()) || StrUtil.isBlank(user.getPassword())) {
            return Result.error("帐号或密码不能为空");
        }
     user  = userService.loginUser(user);
    return Result.success(user);
    }

2.访问user的服务层的loginUSer,先通过user参数,查询对应的user给dbuser,判断是否存在,和user参数的密码和查到的密码是否相同。都满足则创建token,把token通过set方法给dbuser,返回给前端

 @Override
    public User loginUser(User user) {

      User dbuser =  userMapper.selectByUsername(user.getUsername());
      if(dbuser == null){
          throw new ServiceException("用户名或密码错误");
      }
      if(!user.getPassword().equals(dbuser.getPassword())){
          throw new ServiceException("用户名或密码错误");
      }
        String token = TokenUtils.createToken(dbuser.getId().toString(), dbuser.getPassword());
        dbuser.setToken(token);
        return dbuser;
    }

这里面用到了TokenUtils的createToken方法,就是生成token的地方

@Component // 标记此类为Spring组件,使其可以被Spring管理
public class TokenUtils {

    // 声明一个静态的UserMapper,用于在静态方法中访问用户数据
    private static UserMapper staticUserMapper;

    // 注入UserMapper实例
    @Resource
    UserMapper userMapper;

    // 在类实例化后,设置静态的UserMapper
    @PostConstruct // 标记此方法在构造函数后自动执行
    public void setUserService() {
        staticUserMapper = userMapper; // 将实例UserMapper赋值给静态变量
    }

    /**
     * 生成token
     *
     * @param userId 用户ID
     * @param sign   用于签名的密钥
     * @return 生成的token字符串
     */
    public static String createToken(String userId, String sign) {
        // 创建JWT并设置载荷
        return JWT.create()
                .withAudience(userId) // 将用户ID存储在token的载荷中
                .withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 设置token有效期为2小时
                .sign(Algorithm.HMAC256(sign)); // 使用指定的密钥签名token
    }

   
}

三、随后的请求都要验证token

上面我们已经说到第一次请求,也就是登录时会生成token,返回给前端。前端会保存在浏览器中,以后每次header里面都会携带这个token,那么现在就开始第二次请求。

1.验证token过程

因为拦截器的作用,发过来的http请求会被拦截,以验证规则。

当前端请求发送到后端会首先进到这里验证token,而不是直接访问@GetMapping("*请求的接口*")

首先,会从请求头拿token,如果没有则拦截,否则 验证token是否为空,如果为空拦截,

不为空后,解析token中user的id,然后查询数据库是否有这个user,否则拦截,当查到后,

使用用户密码生成一个验证器(为什么是密码呢?因为上面我们生成token时就是用密码作为密钥),与传过来的token进行验证,如果通过则,验证成功,不拦截请求,访问@GetMapping("*请求的接口*")

/**
 * 功能:JWT 拦截器,用于对请求进行身份验证
 * 作者:lhp
 * 日期:2024/9/26 23:01
 */
public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private UserMapper userMapper; // 自动注入 UserMapper,用于数据库操作

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 从请求头中获取 token
        String token = request.getHeader("token");
        // 如果请求头中没有 token,则尝试从请求参数中获取
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }

       

        // 执行认证,首先检查 token 是否为空
        if (StrUtil.isBlank(token)) {
            // 如果 token 为空,抛出未授权异常
            throw new ServiceException("401", "请登录");
        }

        // 获取 token 中的用户 ID
        String userId;
        try {
            userId = JWT.decode(token).getAudience().get(0); // 解码 token,获取用户 ID
        } catch (JWTDecodeException j) {
            // 解码失败,抛出未授权异常
            throw new ServiceException("401", "请登录");
        }

        // 根据 token 中的 userId 查询数据库,获取用户信息
        User user = userMapper.selectById(Integer.valueOf(userId));
        if (user == null) {
            // 如果用户不存在,抛出未授权异常
            throw new ServiceException("401", "请登录");
        }

        // 使用用户密码生成 JWTVerifier,用于验证 token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            // 验证 token 的有效性
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            // 验证失败,抛出未授权异常
            throw new ServiceException("401", "请登录");
        }

        // 所有验证通过,返回 true,表示请求可以继续
        return true;
    }
}

至此就是Jwt的整个流程。以下是拦截器的代码,这个配置类的主要功能是设置一个 JWT 拦截器,用于拦截所有的 HTTP 请求,以便于进行身份验证,但对 /login 请求路径不进行拦截

/**
 * 功能:拦截器配置类,用于配置和注册自定义的拦截器
 * 作者:lhp
 * 日期:2024/9/26 23:15
 */
@Configuration // 标记该类为配置类,Spring 会在运行时自动识别并加载该类的 Bean 定义
public class InterceptorConfig extends WebMvcConfigurationSupport {

    /**
     * 重写 addInterceptors 方法,用于添加自定义拦截器
     * 
     * @param registry 拦截器注册中心,用于注册自定义拦截器
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // 注册 JwtInterceptor 拦截器
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**") // 拦截所有请求路径
                .excludePathPatterns("/login"); // 排除登录路径,不拦截登录请求
        
        // 调用父类的 addInterceptors 方法,确保其他配置能够正常工作
        super.addInterceptors(registry);
    }

    /**
     * 定义 JwtInterceptor Bean,Spring 会自动管理该 Bean 的生命周期
     * 
     * @return JwtInterceptor 实例
     */
    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor(); // 创建并返回 JwtInterceptor 的新实例
    }
}
相关推荐
魔道不误砍柴功15 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_23415 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨18 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
种树人2024081918 分钟前
如何在 Spring Boot 中启用定时任务
spring boot
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq