You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot REST API:如何用UUID作为@PathVariable隐藏敏感数据?

用UUID解决删除接口ID暴露风险的两种实现(Spring Data JPA + MariaDB)

你的删除接口用自增Long ID当路径参数,确实存在风险——攻击者可以通过遍历连续ID(比如1、2、3...)批量删除数据,UUID就是用来解决这个问题的。具体实现分两种思路,看你的场景选:

思路一:直接把UUID设为主键

这种是把原来的Long主键彻底换成UUID,对外暴露的就是UUID,从根源上避免自增ID的遍历问题。

1. 修改实体类

把主键字段改成UUID类型,用JPA注解自动生成:

import jakarta.persistence.*;
import java.util.UUID;

@Entity
@Table(name = "subscriptions")
public class Subscription {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    // 其他业务字段...

    // 补全Getter、Setter
}

2. 更新Repository

把Repository的主键泛型换成UUID:

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;

public interface SubscriptionRepository extends JpaRepository<Subscription, UUID> {
}

3. 调整接口和Service

把接口的路径参数改成UUID,Service层对应调整:

@DeleteMapping("/{uuid}")
public ResponseEntity<String> deleteSubscription(@PathVariable UUID uuid) {
    subscriptionService.deleteSubscription(uuid);
    return ResponseEntity.noContent().build();
}

数据库适配(MariaDB)

MariaDB可以用CHAR(36)存储UUID,10.7+版本支持原生UUID类型,建表语句示例:

CREATE TABLE subscriptions (
    id CHAR(36) NOT NULL PRIMARY KEY,
    -- 其他字段...
);

思路二:保留Long主键,新增UUID作为对外标识

如果你的项目已经有大量历史数据,不想改动原有主键结构,可以新增一个UUID字段专门对外暴露,内部还是用Long ID操作数据库。

1. 实体类加UUID字段

新增一个唯一、不可修改的UUID字段,用@PrePersist自动生成:

import jakarta.persistence.*;
import java.util.UUID;

@Entity
@Table(name = "subscriptions")
public class Subscription {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false, updatable = false)
    private UUID publicId;

    // 其他业务字段...

    // 保存前自动生成UUID
    @PrePersist
    public void generatePublicId() {
        if (publicId == null) {
            publicId = UUID.randomUUID();
        }
    }

    // 补全Getter、Setter
}

2. Repository加查询方法

新增根据publicId查找实体的方法:

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
import java.util.Optional;

public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
    Optional<Subscription> findByPublicId(UUID publicId);
}

3. Service层调整删除逻辑

先通过publicId找到实体,再用内部Long ID删除:

@Service
public class SubscriptionService {
    private final SubscriptionRepository subscriptionRepository;

    // 构造注入
    public SubscriptionService(SubscriptionRepository subscriptionRepository) {
        this.subscriptionRepository = subscriptionRepository;
    }

    public void deleteSubscription(UUID publicId) {
        Subscription subscription = subscriptionRepository.findByPublicId(publicId)
                .orElseThrow(() -> new RuntimeException("订阅不存在"));
        subscriptionRepository.deleteById(subscription.getId());
    }
}

4. 接口修改

路径参数换成UUID:

@DeleteMapping("/{publicId}")
public ResponseEntity<String> deleteSubscription(@PathVariable UUID publicId) {
    subscriptionService.deleteSubscription(publicId);
    return ResponseEntity.noContent().build();
}

两种方案怎么选

  • 选思路一:适合新项目或者能接受数据迁移的场景,结构更简洁,不用额外字段。
  • 选思路二:适合已有大量历史数据的老项目,兼容原有主键,不用改动旧数据。

核心逻辑都是对外暴露无规律、不可遍历的UUID,让攻击者没法靠猜ID恶意操作。

内容的提问来源于stack exchange,提问作者madalin1Q

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 21:00:01