2 Комити 7b393180fa ... 15af8e772d

Аутор SHA1 Порука Датум
  zoujiajian 15af8e772d fix 修复小店登录爆破返回 пре 1 недеља
  zoujiajian a32aeb952d fix 修复系统爆破性谷歌登录、登录地址,系统报错信息 пре 1 недеља

+ 45 - 43
netflix-service/src/main/java/com/cyksj/service/cps/shop/YhShopManageServiceImpl.java

@@ -26,6 +26,7 @@ import com.cyksj.service.mail.MailService;
 import com.cyksj.service.pay.TransferFuncService;
 import com.cyksj.service.user.BadIntentionOpService;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -40,6 +41,7 @@ import java.util.Optional;
  *创建时间:2023/9/19 17:36
  */
 @Service
+@Slf4j
 @RequiredArgsConstructor
 public class YhShopManageServiceImpl implements YhShopManageService{
 	private final YhShopMapper yhShopMapper;
@@ -64,49 +66,49 @@ public class YhShopManageServiceImpl implements YhShopManageService{
 
 	@Override
 	public void getLoginCode(Integer type, String login, Integer cid, String verifyCode, String ticket, String randStr) {
-		if (type > 2) {
-			throw BusinessRuntimeException.getInstance("");
-		}
-		if (cid == null) {
-			cid = 86;
-		}
-		String phone = null;
-		String email = null;
-		if (type == 1) phone = login;
-		else if (type == 2) email = login;
-		User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
-				.eq(StrUtil.isNotBlank(phone), User::getLoginPhone, phone)
-				.eq(StrUtil.isNotBlank(email), User::getEmail, email).last("limit 1"));
-		if (user == null) throw BusinessRuntimeException.getInstance("登录信息不存在");
-		//是否是店铺主
-		YhShop yhShop = yhShopMapper.selectOne(Wrappers.lambdaQuery(YhShop.class)
-				.eq(YhShop::getVerifyStatus, YhShop.Status.success)
-				.eq(YhShop::getUserId, user.getId()).last("limit 1"));
-		if (yhShop == null) {
-			throw BusinessRuntimeException.getInstance("您不是银河小店店铺主");
-		}
-
-		String CACHE_LOGIN_KEY = RedisService.key.YH_SHOP_BACK_LOGIN_KEY.getName()  + login;
-		Object o = redisService.get(CACHE_LOGIN_KEY);
-		Long expire = redisService.getExpire(CACHE_LOGIN_KEY);
-		if (o != null && expire >= 3 * 60 - 10) {
-			throw BusinessRuntimeException.getInstance("获取验证码过于频繁");
-		}
-		String code = StringUtil.getRandomCodeStr(6);
-		if (type == 1) {
-			if (StrUtil.isEmpty(login) || (86 == cid && !Validator.isMobile(phone))) {
-				throw BusinessRuntimeException.getInstance("请输入正确的手机号");
-			}
-			//是否是恶意用户
-			badIntentionOpService.checkHandleBadUserOp(phone, verifyCode, ticket, randStr, request);
-			SmsUtil.sendTencent(cid, login, code);
-		} else if (type == 2) {
-			if (!Validator.isEmail(login)) {
-				throw BusinessRuntimeException.getInstance("请输入正确的邮箱");
-			}
-			mailService.sendLoginCodeEmailToPeople(code, login);
-		}
-		redisService.set(CACHE_LOGIN_KEY, code, RedisService.key.YH_SHOP_BACK_LOGIN_KEY.getTimeout());
+        if (type == null || (type != 1 && type != 2)) {
+            throw BusinessRuntimeException.getInstance("不支持的登录方式");
+        }
+        if (cid == null) cid = 86;
+        if (StrUtil.isBlank(login) || login.length() > 254) {
+            throw BusinessRuntimeException.getInstance("请输入正确的登录账号");
+        }
+        if (type == 1 && (cid <= 0 || (cid == 86 && !Validator.isMobile(login)))) {
+            throw BusinessRuntimeException.getInstance("请输入正确的手机号");
+        }
+        if (type == 2 && !Validator.isEmail(login)) {
+            throw BusinessRuntimeException.getInstance("请输入正确的邮箱");
+        }
+        // 所有合法账号在查询前限速,不让账号存在性决定限速响应。
+        // 使用直连地址,不信任客户端可伪造的X-Forwarded-For。
+        String ip = request.getRemoteAddr();
+        String accountHash = cn.hutool.crypto.digest.DigestUtil.sha256Hex(type + ":" + cid + ":" + login);
+        if (!redisService.setNx("yhShop:loginCode:ip:" + ip, "1", 2L)) return;
+        if (!redisService.setNx("yhShop:loginCode:account:" + accountHash, "1", 60L)) return;
+        if (type == 1) {
+            badIntentionOpService.checkHandleBadUserOp(login, verifyCode, ticket, randStr, request);
+        }
+        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
+                .eq(type == 1, User::getLoginPhone, login)
+                .eq(type == 2, User::getEmail, login).last("limit 1"));
+        if (user == null) return;
+        YhShop yhShop = yhShopMapper.selectOne(Wrappers.lambdaQuery(YhShop.class)
+                .eq(YhShop::getVerifyStatus, YhShop.Status.success)
+                .eq(YhShop::getUserId, user.getId()).last("limit 1"));
+        if (yhShop == null) return;
+        String cacheKey = RedisService.key.YH_SHOP_BACK_LOGIN_KEY.getName() + login;
+        String code = StringUtil.getRandomCodeStr(6);
+        try {
+            if (type == 1) {
+                SmsUtil.sendTencent(cid, login, code);
+            } else {
+                if (mailService.sendLoginCodeEmailToPeople(code, login) == null) return;
+            }
+            redisService.set(cacheKey, code, RedisService.key.YH_SHOP_BACK_LOGIN_KEY.getTimeout());
+        } catch (Exception e) {
+            // 发送渠道错误不外返,避免只在真实店主账号出现可区分的响应。
+            log.error("小店登录验证码发送失败", e);
+        }
 	}
 
 	@Override

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/user/UserService.java

@@ -52,6 +52,8 @@ public interface UserService extends IService<User> {
 	 */
 	User webLogin(LoginReq loginReq, HttpServletRequest request) throws Exception;
 
+	User googleLogin(LoginReq loginReq, String credential, HttpServletRequest request) throws Exception;
+
 	String bindWxAccountRedirect(String code, String state) throws Exception;
 
 	/**

+ 39 - 0
netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java

@@ -1,5 +1,7 @@
 package com.cyksj.service.user.impl;
 
+import cn.hutool.http.HttpUtil;
+import com.cyksj.model.response.GoogleLoginRep;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateTime;
@@ -473,6 +475,43 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 
 	@Override
 	public User webLogin(LoginReq loginReq, HttpServletRequest request) throws Exception {
+        if (loginReq == null || loginReq.getType() == null
+                || (loginReq.getType() != 1 && loginReq.getType() != 2)) {
+            throw BusinessRuntimeException.getInstance("不支持的登录方式");
+        }
+        return loginWithVerifiedIdentity(loginReq, request);
+    }
+
+    @Override
+    public User googleLogin(LoginReq loginReq, String credential, HttpServletRequest request) throws Exception {
+        if (loginReq == null || StrUtil.isBlank(credential)) {
+            throw BusinessRuntimeException.getInstance("Google身份验证失败");
+        }
+        // 身份只能来自服务端验证结果,不能相信客户端的type/email/subject。
+        GoogleLoginRep identity;
+        try {
+            String url = "https://galaxyva.com/8081/api/auth/google/login/inner?credential="
+                    + java.net.URLEncoder.encode(credential, "UTF-8");
+            String result = HttpUtil.post(url, new java.util.HashMap<>(), 10000);
+            java.util.Map<String, Object> response = Jsons.toMap(result);
+            if (!"10000".equals(String.valueOf(response.get("code")))) {
+                throw new IllegalArgumentException("Verification rejected");
+            }
+            identity = Jsons.parseObject(response.get("data"), GoogleLoginRep.class);
+            if (identity == null || StrUtil.isBlank(identity.getEmail()) || StrUtil.isBlank(identity.getSubject())) {
+                throw new IllegalArgumentException("Missing verified identity");
+            }
+        } catch (Exception e) {
+            // 不记录credential或验证服务响应。
+            throw BusinessRuntimeException.getInstance("Google身份验证失败");
+        }
+        loginReq.setType(3);
+        loginReq.setEmail(identity.getEmail());
+        loginReq.setSubject(identity.getSubject());
+        return loginWithVerifiedIdentity(loginReq, request);
+    }
+
+    private User loginWithVerifiedIdentity(LoginReq loginReq, HttpServletRequest request) throws Exception {
 		LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).last("limit 1");
 		if (loginReq.getType() == null || loginReq.getType() < 1 || loginReq.getType() > 3) {
 			throw BusinessRuntimeException.getInstance("系统异常,请刷新页面重新操作");

+ 1 - 1
netflix-web/src/main/java/com/cyksj/config/SaTokenConfigure.java

@@ -20,7 +20,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
 	@Override
 	public void addInterceptors(InterceptorRegistry registry) {
 		registry.addInterceptor(new SaRouteInterceptor())
-				.addPathPatterns(List.of("/manage/**", "/manager/**", "/sys/update", "/sys/delete/**"))
+				.addPathPatterns(List.of("/manage/**", "/manager/**", "/sys/post", "/sys/update", "/sys/delete/**"))
 				.excludePathPatterns(List.of("/manage/admin/login",
 						"/manage/admin/get/loginInfo",
 						"/manage/corp/**",

+ 5 - 19
netflix-web/src/main/java/com/cyksj/web/GlobalExceptionHandler.java

@@ -5,7 +5,6 @@ import cn.dev33.satoken.exception.NotPermissionException;
 import cn.dev33.satoken.exception.NotRoleException;
 import cn.hutool.core.lang.Validator;
 import com.cyksj.common.exception.BusinessRuntimeException;
-import com.cyksj.common.util.StringUtil;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayApiCode;
 import com.cyksj.enums.GatewayResponse;
@@ -39,22 +38,9 @@ public class GlobalExceptionHandler {
 
     @ExceptionHandler(value = {Exception.class})
     public Result<String> handleNoteException(Exception e) {
-        StringBuilder builder = new StringBuilder(32);
-        builder.append(request.getRequestURI()).append(" -- > ");
-
-        StackTraceElement[] trace = e.getStackTrace();
-        log.info("{}",e);
-        for (StackTraceElement element : trace) {
-            if (StringUtil.startsWith(element.getClassName(), "com.cyksj")) {
-                builder.append(element.getFileName())
-                        .append("[").append(element.getLineNumber()).append("]")
-                        .append("&");
-            }
-        }
-        builder.deleteCharAt(builder.length() - 1);
-
-        String message = e.getMessage();
-        String value = builder.toString();
+        String requestId = java.util.UUID.randomUUID().toString();
+        log.error("Request failed requestId={} path={}", requestId, request.getRequestURI(), e);
+        String message = e instanceof BusinessRuntimeException ? e.getMessage() : "系统异常,请稍后重试";
         GatewayApiCode apiCode = null;
         if (e instanceof BusinessRuntimeException) {
             BusinessRuntimeException ee = (BusinessRuntimeException) e;
@@ -84,11 +70,11 @@ public class GlobalExceptionHandler {
             NotPermissionException ee = (NotPermissionException) e;
             apiCode = GatewayApiCode.CMS_USER_NO_PERMISSION;
         }
-        log.error("全局错误处理: \n Cause: {} \n Value: {}", message, value);
+
         if (apiCode != null) {
             return GatewayResponse.FAIL.newBuilder().buildGatewayCode(apiCode).toResult();
         }
-        return GatewayResponse.FAIL.newBuilder().setMsg(message).toResult(value);
+        return GatewayResponse.FAIL.newBuilder().setMsg(message).toResult("requestId=" + requestId);
     }
 
     @ExceptionHandler(value = ConstraintViolationException.class)

+ 1 - 9
netflix-web/src/main/java/com/cyksj/web/controller/cps/CpsBackLoginController.java

@@ -27,7 +27,6 @@ import com.cyksj.model.dto.CpsAuthDto;
 import com.cyksj.model.dto.GzhOAuth2UserInfo;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.request.LoginReq;
-import com.cyksj.model.response.GoogleLoginRep;
 import com.cyksj.model.response.UserAuthLoginResp;
 import com.cyksj.model.views.UserInfoView;
 import com.cyksj.redis.RedisService;
@@ -246,14 +245,7 @@ public class CpsBackLoginController {
 	 */
 	@PostMapping("/google/login")
 	public Result<UserAuthLoginResp> googleLogin(LoginReq loginReq, String credential) throws Exception {
-		log.info("google loginReq:{},credential:{}", loginReq, credential);
-		String result = HttpUtil.post("https://galaxyva.com/8081/api/auth/google/login/inner?credential=" + credential, new HashMap<>());
-		log.info("google result:{}", result);
-		GoogleLoginRep googleLoginRep = Jsons.parseObject(Jsons.toMap(result).get("data"), GoogleLoginRep.class);
-		loginReq.setType(3);
-		loginReq.setEmail(googleLoginRep.getEmail());
-		loginReq.setSubject(googleLoginRep.getSubject());
-		User user = userService.webLogin(loginReq, request);
+		User user = userService.googleLogin(loginReq, credential, request);
 
 		//是否非cps分销用户
 		UserDistribute userDistribute = userDistributeMapper.selectOne(Wrappers.lambdaQuery(UserDistribute.class)

+ 3 - 3
netflix-web/src/main/java/com/cyksj/web/controller/manage/AdminController.java

@@ -73,17 +73,17 @@ public class AdminController {
                 new QueryWrapper<Admin>().lambda().eq(Admin::getPhone,adminRequest.getPhone()));
 
         if (null == admin) {
-            throw BusinessRuntimeException.getInstance("该用户不存在.");
+            throw BusinessRuntimeException.getInstance("账号或凭据错误");
         }
         if (null == admin.getStatus() || !admin.getStatus()) {
-            throw BusinessRuntimeException.getInstance("无登录权限.");
+            throw BusinessRuntimeException.getInstance("账号或凭据错误");
         }
 
         // 对比密码
         String encodePasscode = Codec.DoDigest.custom().setAlgorithm(Codec.DoDigest.Algorithm.SHA256).setStringData(adminRequest.getPassWord()).toBase64String();
 
         if (!StringUtils.equals(admin.getPassWord(), encodePasscode)) {
-            throw BusinessRuntimeException.getInstance("密码有误.");
+            throw BusinessRuntimeException.getInstance("账号或凭据错误");
         }
         return GatewayResponse.SUCCESS.newBuilder().toResult();
     }

+ 1 - 2
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/shop/YhShopLoginController.java

@@ -64,10 +64,9 @@ public class YhShopLoginController {
 	 * 获取登录验证码
 	 */
 	@GetMapping("/get/LoginCode")
-	@NoSubmit
 	public Result getLoginCode(@RequestParam(defaultValue = "1") Integer type, String login, Integer cid, String verifyCode, String ticket, String randStr) {
 		yhShopManageService.getLoginCode(type, login, cid, verifyCode, ticket, randStr);
-		return GatewayResponse.SUCCESS.newBuilder().toResult();
+		return GatewayResponse.SUCCESS.newBuilder().setMsg("如果账号符合登录条件,验证码将发送至对应账号").toResult();
 	}
 
 	/**

+ 2 - 0
netflix-web/src/main/java/com/cyksj/web/controller/sys/SysConfigController.java

@@ -112,6 +112,8 @@ public class SysConfigController {
 	@PostMapping("/post")
 	@Log(module = "系统设置/新增配置", businessType = BusinessType.POST, isSaveRequestData = true)
 	public Result<SysConfig> post(@RequestBody SysConfig sysConfig) {
+        StpUtil.checkLogin();
+        sysConfig.setId(null);
 		sysConfigService.save(sysConfig);
 		return GatewayResponse.SUCCESS.newBuilder().toResult(sysConfig);
 	}

+ 28 - 4
netflix-web/src/main/java/com/cyksj/web/controller/user/AddressController.java

@@ -52,6 +52,7 @@ public class AddressController {
         if(address.getIsDefault() != null && address.getIsDefault() ){
             addressService.update(Wrappers.lambdaUpdate(Address.class).in(Address::getUserId,userIdList).set(Address::getIsDefault,false));
         }
+        address.setId(null);
         address.setUserId(userId);
         addressService.save(address);
         return GatewayResponse.SUCCESS.newBuilder().toResult(address);
@@ -63,11 +64,34 @@ public class AddressController {
 
         long userId = StpUserUtil.getLoginIdAsLong();
         List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
-        if(address.getIsDefault() != null && address.getIsDefault() ){
-            addressService.update(Wrappers.lambdaUpdate(Address.class).in(Address::getUserId,userIdList).set(Address::getIsDefault,false));
+        if (address.getId() == null || userIdList == null || userIdList.isEmpty()) {
+            throw new BusinessRuntimeException("地址不存在或无权操作");
         }
-
-        addressService.updateById(address);
+        Address existing = addressService.getOne(Wrappers.lambdaQuery(Address.class)
+                .eq(Address::getId, address.getId()).in(Address::getUserId, userIdList));
+        if (existing == null) {
+            throw new BusinessRuntimeException("地址不存在或无权操作");
+        }
+        // 只复制允许编辑的字段,拒绝userId和BaseEntity字段的批量赋值。
+        Address changes = new Address();
+        changes.setReceiver(address.getReceiver());
+        changes.setPhone(address.getPhone());
+        changes.setProvince(address.getProvince());
+        changes.setCity(address.getCity());
+        changes.setDistrict(address.getDistrict());
+        changes.setAddress(address.getAddress());
+        changes.setCode(address.getCode());
+        changes.setIsDefault(address.getIsDefault());
+        if (!addressService.update(changes, Wrappers.lambdaUpdate(Address.class)
+                .eq(Address::getId, existing.getId()).in(Address::getUserId, userIdList))) {
+            throw new BusinessRuntimeException("地址不存在或无权操作");
+        }
+        if (Boolean.TRUE.equals(address.getIsDefault())) {
+            addressService.update(Wrappers.lambdaUpdate(Address.class)
+                    .in(Address::getUserId, userIdList).ne(Address::getId, existing.getId())
+                    .set(Address::getIsDefault, false));
+        }
+        address = addressService.getById(existing.getId());
         return GatewayResponse.SUCCESS.newBuilder().toResult(address);
     }
 

+ 1 - 9
netflix-web/src/main/java/com/cyksj/web/controller/user/AuthorizationController.java

@@ -25,7 +25,6 @@ import com.cyksj.model.dto.*;
 import com.cyksj.model.entity.PhoneCode;
 import com.cyksj.model.entity.User;
 import com.cyksj.model.request.LoginReq;
-import com.cyksj.model.response.GoogleLoginRep;
 import com.cyksj.model.response.UserAuthLoginResp;
 import com.cyksj.model.response.WxGzhQrCodeTicketRep;
 import com.cyksj.redis.RedisService;
@@ -587,14 +586,7 @@ public class AuthorizationController {
 
     @PostMapping("/google/login")
     public Result<UserAuthLoginResp> googleLogin(LoginReq loginReq,String credential) throws Exception {
-        log.info("google loginReq:{},credential:{}", loginReq, credential);
-        String result = HttpUtil.post("https://galaxyva.com/8081/api/auth/google/login/inner?credential=" + credential, new HashMap<>());
-        log.info("google result:{}",result);
-        GoogleLoginRep googleLoginRep = Jsons.parseObject(Jsons.toMap(result).get("data"), GoogleLoginRep.class);
-        loginReq.setType(3);
-        loginReq.setEmail(googleLoginRep.getEmail());
-        loginReq.setSubject(googleLoginRep.getSubject());
-        User user = userService.webLogin(loginReq, request);
+        User user = userService.googleLogin(loginReq, credential, request);
 
         Optional.ofNullable(user.getIsBlack())
                 .ifPresent(black -> {