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

Spring应用JWT越权访问问题及解决方案咨询

解决JWT越权访问其他客户数据的问题

这个问题的核心原因很明确:你的JWT只包含了用户名信息,没有绑定用户所属的客户ID,而且在请求接口时没有校验当前用户是否有权限访问指定客户的资源。下面是具体的解决步骤,结合你的现有代码进行修改:

1. 在JWT中添加客户ID(Customer ID)的Claim

首先,需要在生成JWT的时候,把用户关联的客户ID加入到Token的Claims中。这样后续就能从Token里直接拿到用户所属的客户标识,用于权限校验。

修改你的generateJwtToken方法:

public String generateJwtToken(Authentication auth) {
    UserPrinciple userPrinciple = (UserPrinciple) auth.getPrincipal();
    return Jwts.builder()
            .setSubject(userPrinciple.getUsername())
            .claim("customerId", userPrinciple.getCustomerId()) // 新增客户ID Claim
            .setIssuedAt(new Date())
            .setExpiration(expiryDate())
            .signWith(SignatureAlgorithm.HS512, "secretKey")
            .compact();
}

注意:你需要确保UserPrinciple类已经包含customerId字段以及对应的getCustomerId()方法。如果还没有,先修改UserPrinciple:

public class UserPrinciple implements UserDetails {
    // 原有字段
    private Long id;
    private String username;
    private String password;
    // 新增客户ID字段
    private Long customerId;
    private Collection<? extends GrantedAuthority> authorities;

    // 构造方法需要包含customerId
    public UserPrinciple(Long id, String username, String password, Long customerId, Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.customerId = customerId;
        this.authorities = authorities;
    }

    // 新增getter
    public Long getCustomerId() {
        return customerId;
    }

    // 其他UserDetails接口方法的实现...
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

同时,在userService.loadUserByUsername方法中,查询用户信息时要同时获取对应的customerId,并传入UserPrinciple的构造函数中。

2. 从JWT中解析客户ID(可选,用于过滤器中提前校验)

如果你想在过滤器层面就提前做初步校验,可以添加一个解析客户ID的方法:

public Long getCustomerIdFromJwtToken(String token) {
    return Jwts.parser()
            .setSigningKey("secretKey")
            .parseClaimsJws(token)
            .getBody().get("customerId", Long.class);
}

然后在doFilterInternal方法中,你可以把客户ID和用户信息绑定到SecurityContext中,同时校验一致性:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        String jwt = getJwt(request);
        if (jwt!=null && jwtProvider.validateJwtToken(jwt)) {
            String username = jwtProvider.getUserNameFromJwtToken(jwt);
            Long customerIdFromToken = jwtProvider.getCustomerIdFromJwtToken(jwt); // 获取Token中的客户ID
            UserDetails userDetails = userService.loadUserByUsername(username);
            
            // 校验Token中的客户ID与数据库中用户绑定的客户ID是否一致
            UserPrinciple userPrinciple = (UserPrinciple) userDetails;
            if (!userPrinciple.getCustomerId().equals(customerIdFromToken)) {
                throw new AccessDeniedException("Invalid customer association in JWT");
            }
            
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    } catch (Exception e) {
        logger.error("Can NOT set user authentication -> Message: {}", e.getMessage());
    }
    filterChain.doFilter(request, response);
}

3. 接口层面的权限校验(核心步骤)

这是阻止越权访问的关键:在每个需要客户隔离的接口中,校验当前用户的客户ID和请求中指定的客户ID是否一致。

方式一:手动校验(适合简单场景)

在Controller方法中直接对比:

@GetMapping("/api")
public ResponseEntity<?> getCustomerData(@RequestParam("customer") Long customerId) {
    // 获取当前登录用户的信息
    UserPrinciple currentUser = (UserPrinciple) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    
    // 校验客户ID是否匹配
    if (!currentUser.getCustomerId().equals(customerId)) {
        throw new AccessDeniedException("You are not authorized to access this customer's data");
    }
    
    // 正常查询并返回数据
    CustomerData data = customerService.getCustomerData(customerId);
    return ResponseEntity.ok(data);
}

方式二:使用Spring Security的方法级注解(更优雅)

如果你已经启用了方法级安全(通过@EnableGlobalMethodSecurity(prePostEnabled = true)),可以用@PreAuthorize注解自动校验:

@PreAuthorize("#customerId == authentication.principal.customerId")
@GetMapping("/api")
public ResponseEntity<?> getCustomerData(@RequestParam("customer") Long customerId) {
    // 只有当客户ID匹配时,才会执行这里的逻辑
    CustomerData data = customerService.getCustomerData(customerId);
    return ResponseEntity.ok(data);
}

4. 额外的安全建议

  • 不要硬编码JWT密钥:把secretKey放到配置文件(比如application.properties)中,通过@Value注入,避免代码泄露密钥。
  • 设置合理的Token过期时间:你当前设置的15分钟是合理的,不过可以根据业务需求调整,同时实现Token刷新机制。
  • 最小权限原则:确保每个用户只拥有访问自身客户数据的权限,不要给过高的权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 19:22:35