Просмотр исходного кода

fix: 接口越权加固——订单/预约/发票/退款/用户/车辆全量补 userId 归属校验,删除用户列表与新增接口,改绑车牌限本人名下,手机号禁改,优惠券绑定限有效未使用状态

skyline 3 недель назад
Родитель
Сommit
db3549a95d

+ 27 - 3
miniapp/src/main/java/com/kym/miniapp/controller/InvoiceTitleController.java

@@ -29,6 +29,7 @@ public class InvoiceTitleController {
         if (title == null) {
             throw new BusinessException("抬头不存在");
         }
+        checkOwnership(title);
         return R.success(title);
     }
 
@@ -46,6 +47,7 @@ public class InvoiceTitleController {
         if (existing == null) {
             throw new BusinessException("抬头不存在");
         }
+        checkOwnership(existing);
         title.setId(id);
         title.setUserId(existing.getUserId());
         invoiceTitleService.updateById(title);
@@ -54,13 +56,13 @@ public class InvoiceTitleController {
 
     @DeleteMapping("/{id}")
     R<?> delete(@PathVariable Long id) {
-        invoiceTitleService.removeById(id);
+        deleteOwnedTitle(id);
         return R.success();
     }
 
     @PostMapping("/{id}/delete")
     R<?> deleteByPost(@PathVariable Long id) {
-        invoiceTitleService.removeById(id);
+        deleteOwnedTitle(id);
         return R.success();
     }
 
@@ -70,7 +72,29 @@ public class InvoiceTitleController {
         if (title == null) {
             throw new BusinessException("抬头不存在");
         }
-        invoiceTitleService.setDefault(title.getUserId(), id);
+        checkOwnership(title);
+        invoiceTitleService.setDefault(StpUtil.getLoginIdAsLong(), id);
         return R.success();
     }
+
+    /**
+     * 校验抬头归属,防止越权操作他人抬头
+     */
+    private void checkOwnership(InvoiceTitle title) {
+        if (!title.getUserId().equals(StpUtil.getLoginIdAsLong())) {
+            throw new BusinessException("无权操作该抬头");
+        }
+    }
+
+    /**
+     * 删除当前用户名下的抬头
+     */
+    private void deleteOwnedTitle(Long id) {
+        var title = invoiceTitleService.getById(id);
+        if (title == null) {
+            throw new BusinessException("抬头不存在");
+        }
+        checkOwnership(title);
+        invoiceTitleService.removeById(id);
+    }
 }

+ 6 - 31
miniapp/src/main/java/com/kym/miniapp/controller/UserController.java

@@ -3,8 +3,7 @@ package com.kym.miniapp.controller;
 import cn.dev33.satoken.stp.StpUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.kym.common.R;
-import com.kym.common.utils.IDGenerator;
-import com.kym.entity.common.PageBean;
+import com.kym.common.exception.BusinessException;
 import com.kym.entity.miniapp.User;
 import com.kym.entity.miniapp.queryParams.WxLoginParams;
 import com.kym.entity.miniapp.vo.UserVo;
@@ -29,12 +28,9 @@ public class UserController {
 
     private final CollectService collectService;
 
-    private final IDGenerator idGenerator;
-
-    public UserController(UserService userService, CollectService collectService, IDGenerator idGenerator) {
+    public UserController(UserService userService, CollectService collectService) {
         this.userService = userService;
         this.collectService = collectService;
-        this.idGenerator = idGenerator;
     }
 
     /**
@@ -82,40 +78,19 @@ public class UserController {
     }
 
     /**
-     * 获取单个用户信息
+     * 获取当前用户信息(仅能查看自己)
      *
      * @param id
      * @return
      */
     @GetMapping("/{id}")
     User getUser(@PathVariable("id") Long id) {
+        if (!id.equals(StpUtil.getLoginIdAsLong())) {
+            throw new BusinessException("无权访问该用户信息");
+        }
         return userService.getById(id);
     }
 
-    /**
-     * 用户列表
-     *
-     * @param pageNum
-     * @param pageSize
-     * @return
-     */
-    @GetMapping("listUser")
-    PageBean<UserVo> listUser(@RequestParam int pageNum, @RequestParam int pageSize) {
-        return userService.listUserVo(pageNum, pageSize);
-    }
-
-    /**
-     * 新增用户
-     *
-     * @param user
-     * @return
-     */
-    @PostMapping
-    Boolean saveUser(@RequestBody User user) {
-        user.setId(idGenerator.nextId());
-        return userService.save(user);
-    }
-
     /**
      * 更新用户信息
      *

+ 0 - 2
service/src/main/java/com/kym/service/miniapp/UserService.java

@@ -23,8 +23,6 @@ public interface UserService extends MPJBaseService<User> {
 
     void updateUser(UserVo userVo);
 
-    PageBean<UserVo> listUserVo(Integer pageNum, Integer pageSize);
-
     R<?> wxLogin(WxLoginParams params);
 
     PageBean<CustomUserVo> listCustomUser(CommonQueryParam params);

+ 16 - 1
service/src/main/java/com/kym/service/miniapp/impl/ChargeOrderServiceImpl.java

@@ -234,7 +234,14 @@ public class ChargeOrderServiceImpl extends MyBaseServiceImpl<ChargeOrderMapper,
 
     @Override
     public ChargeOrderVo orderDetailForApp(String startChargeSeq) {
-        var chargeOrder = lambdaQuery().eq(ChargeOrder::getStartChargeSeq, startChargeSeq).one();
+        // 校验订单归属,防止越权查看他人订单
+        var chargeOrder = lambdaQuery()
+                .eq(ChargeOrder::getStartChargeSeq, startChargeSeq)
+                .eq(ChargeOrder::getUserId, StpUtil.getLoginIdAsLong())
+                .one();
+        if (chargeOrder == null) {
+            throw new BusinessException("订单不存在");
+        }
         // 订单对应的优惠信息
         var orderVo = new ChargeOrderVo();
         BeanUtils.copyProperties(chargeOrder, orderVo);
@@ -255,6 +262,14 @@ public class ChargeOrderServiceImpl extends MyBaseServiceImpl<ChargeOrderMapper,
 
     @Override
     public Object orderDiscountDetail(String startChargeSeq, String discountType) {
+        // 校验订单归属,防止越权查看他人订单优惠信息
+        var owned = lambdaQuery()
+                .eq(ChargeOrder::getStartChargeSeq, startChargeSeq)
+                .eq(ChargeOrder::getUserId, StpUtil.getLoginIdAsLong())
+                .exists();
+        if (!owned) {
+            throw new BusinessException("订单不存在");
+        }
         return switch (discountType) {
             case Activity.DISCOUNT_TYPE_服务费折扣权益 ->
                     userRechargeRightsService.getUserOrderRechargeRights(startChargeSeq);

+ 17 - 4
service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java

@@ -121,11 +121,19 @@ public class ChargeServiceImpl implements ChargeService {
      */
     @Override
     public void modifyBookingTime(String startChargeSeq, LocalDateTime startTime) {
-        // 预约充电队列更新
-        var chargeOrder = chargeOrderService.lambdaQuery().eq(ChargeOrder::getStartChargeSeq, startChargeSeq).eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中).one();
+        // 预约充电队列更新(校验订单归属,防止越权修改他人预约)
+        var userId = StpUtil.getLoginIdAsLong();
+        var chargeOrder = chargeOrderService.lambdaQuery()
+                .eq(ChargeOrder::getStartChargeSeq, startChargeSeq)
+                .eq(ChargeOrder::getUserId, userId)
+                .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
+                .one();
         if (chargeOrder == null) {
             throw new BusinessException("预约订单不存在");
         }
+        if (startTime.isBefore(LocalDateTime.now())) {
+            throw new BusinessException("预约时间不能早于当前时间");
+        }
         chargeOrder.setStartTime(startTime);
         chargeOrder.setEndTime(null);
         chargeOrderService.lambdaUpdate().eq(ChargeOrder::getStartChargeSeq, startChargeSeq).set(ChargeOrder::getStartTime, startTime).set(ChargeOrder::getEndTime, null).update();
@@ -250,9 +258,14 @@ public class ChargeServiceImpl implements ChargeService {
                 }
             }
 
-            // 优惠券
+            // 优惠券(仅有效且未使用的券,防止一券多用)
             if (!CommUtil.isEmptyOrNull(userCouponId)) {
-                var userCoupon = userCouponService.lambdaQuery().eq(UserCoupon::getUserId, userId).eq(UserCoupon::getId, userCouponId).one();
+                var userCoupon = userCouponService.lambdaQuery()
+                        .eq(UserCoupon::getUserId, userId)
+                        .eq(UserCoupon::getId, userCouponId)
+                        .eq(UserCoupon::getStatus, UserCoupon.STATUS_有效)
+                        .eq(UserCoupon::getUsageStatus, UserCoupon.USAGE_STATUS_未使用)
+                        .one();
                 if (userCoupon != null) {
                     // 设置订单优惠类型
                     order.setDiscountType(Activity.DISCOUNT_TYPE_优惠券);

+ 5 - 0
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -73,6 +73,10 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
         }
         var userId = StpUtil.getLoginIdAsLong();
         var orders = chargeOrderService.getChargeOrdersBySeqs(params.getStartChargeSeqs());
+        // 校验订单归属,防止越权使用他人订单开票
+        if (orders.stream().anyMatch(o -> !o.getUserId().equals(userId))) {
+            throw new BusinessException("存在不属于当前用户的订单");
+        }
 
         if (orders.stream().anyMatch(o -> o.getOrderStatus().equals(ChargeOrder.ORDER_STATUS_未知))) {
             log.error("存在未知状态的订单:{}", orders.stream().filter(o -> o.getOrderStatus().equals(ChargeOrder.ORDER_STATUS_未知)).map(ChargeOrder::getStartChargeSeq).toArray());
@@ -152,6 +156,7 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
         }
 
         chargeOrderService.lambdaUpdate().set(ChargeOrder::getInvoiceStatus, ChargeOrder.INVOICE_STATUS_开票中)
+                .eq(ChargeOrder::getUserId, userId)
                 .in(ChargeOrder::getStartChargeSeq, Arrays.stream(params.getStartChargeSeqs()).toList()).update();
 
         return invoice;

+ 1 - 0
service/src/main/java/com/kym/service/miniapp/impl/InvoiceTitleServiceImpl.java

@@ -45,6 +45,7 @@ public class InvoiceTitleServiceImpl extends MPJBaseServiceImpl<InvoiceTitleMapp
         lambdaUpdate()
                 .set(InvoiceTitle::getIsDefault, true)
                 .eq(InvoiceTitle::getId, titleId)
+                .eq(InvoiceTitle::getUserId, userId)
                 .update();
     }
 }

+ 3 - 0
service/src/main/java/com/kym/service/miniapp/impl/RefundLogServiceImpl.java

@@ -1,5 +1,6 @@
 package com.kym.service.miniapp.impl;
 
+import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
@@ -32,6 +33,8 @@ public class RefundLogServiceImpl extends MPJBaseServiceImpl<RefundLogMapper, Re
 
     @Override
     public PageBean<RefundLog> listRefundLogForApp(CommonQueryParam params) {
+        // 强制使用当前登录用户,防止越权查询他人退款记录
+        params.setUserId(StpUtil.getLoginIdAsLong());
         PageHelper.startPage(params.getPageNum(), params.getPageSize());
         var list = lambdaQuery().eq(RefundLog::getUserId, params.getUserId()).orderByDesc(RefundLog::getCreateTime).list();
         return new PageBean<>(list);

+ 13 - 15
service/src/main/java/com/kym/service/miniapp/impl/UserServiceImpl.java

@@ -218,16 +218,24 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void updateUser(UserVo userVo) {
-        userVo.setId(StpUtil.getSession().getLong("userId"));
+        var userId = StpUtil.getLoginIdAsLong();
+        userVo.setId(userId);
         var user = new User();
         BeanUtils.copyProperties(userVo, user);
+        // 手机号不允许通过该接口修改,只能通过微信手机号授权渠道更新
+        if (!CommUtil.isEmptyOrNull(user.getMobilePhone())) {
+            var dbUser = getById(userId);
+            if (dbUser == null || !user.getMobilePhone().equals(dbUser.getMobilePhone())) {
+                throw new BusinessException("请通过微信手机号授权更新手机号");
+            }
+        }
         if (!CommUtil.isEmptyOrNull(user.getAvatar()) || !CommUtil.isEmptyOrNull(user.getMobilePhone()) || !CommUtil.isEmptyOrNull(user.getNickname())) {
             baseMapper.updateById(user);
         }
         // 更新车牌信息
         if (userVo.getDefaultPlateNo() != null) {
             var car = new Cars();
-            car.setUserId(userVo.getId());
+            car.setUserId(userId);
             car.setPlateNo(userVo.defaultPlateNo);
             // 设置为默认
             car.setIsDefault(true);
@@ -235,11 +243,13 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
                 car.setVin(userVo.getVin());
             }
             // 将用户名下其他车辆设为非默认
-            var cars = carsService.listByMap(Map.of("user_id", userVo.getId()));
+            var cars = carsService.lambdaQuery().eq(Cars::getUserId, userId).list();
             cars.stream().filter(c -> !userVo.getDefaultPlateNo().equals(c.getEngineNo())).peek(s -> s.setIsDefault(false)).collect(Collectors.toList());
             carsService.updateBatchById(cars);
             var wrapper = new QueryWrapper<Cars>();
             wrapper.eq("plate_no", userVo.getDefaultPlateNo());
+            // 只允许操作自己名下的车辆,防止改绑他人车辆
+            wrapper.eq("user_id", userId);
             // MyBatis-Plus 3.5.9+ saveOrUpdate(T, Wrapper) 方法已移除,改为先查询再保存或更新
             var existCar = carsService.getOne(wrapper);
             if (existCar != null) {
@@ -251,18 +261,6 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
         }
     }
 
-    @Override
-    public PageBean<UserVo> listUserVo(Integer pageNum, Integer pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        var userVoList = list().stream().map(user -> {
-            var userVo = new UserVo();
-            BeanUtils.copyProperties(user, userVo);
-            return userVo;
-        }).collect(Collectors.toList());
-        return new PageBean<>(userVoList);
-    }
-
-
     /**
      * 分页查询用户列表
      *