refactor(license): remove server-side package name validation

Package name matching is now entirely the SDK's responsibility.
- DeviceService: drop packageName param from register/verify, delete validatePackageName()
- LicensePublicController: remove matchesPackageName check in resolveAppKey(),
  remove packageName from service calls, add GET /api/license/app-info for
  SDK appKey-only flow to fetch configured package names for local comparison

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-05-23 00:28:51 +08:00
父节点 26261263a0
当前提交 b7c2f0144f
共有 2 个文件被更改,包括 36 次插入48 次删除

查看文件

@ -6,15 +6,15 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.xuqm.common.exception.BusinessException; import com.xuqm.common.exception.BusinessException;
import com.xuqm.common.model.ApiResponse; import com.xuqm.common.model.ApiResponse;
import com.xuqm.common.security.LicenseFileCrypto; import com.xuqm.common.security.LicenseFileCrypto;
import com.xuqm.license.entity.AppLicenseEntity;
import com.xuqm.license.service.AppLicenseService;
import com.xuqm.license.service.DeviceService; import com.xuqm.license.service.DeviceService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@RestController @RestController
@ -22,24 +22,25 @@ import java.util.Map;
public class LicensePublicController { public class LicensePublicController {
private final DeviceService deviceService; private final DeviceService deviceService;
private final AppLicenseService appLicenseService;
public LicensePublicController(DeviceService deviceService) { public LicensePublicController(DeviceService deviceService, AppLicenseService appLicenseService) {
this.deviceService = deviceService; this.deviceService = deviceService;
this.appLicenseService = appLicenseService;
} }
@PostMapping("/register") @PostMapping("/register")
public ResponseEntity<ApiResponse<Map<String, Object>>> register(@Valid @RequestBody RegisterRequest req) { public ResponseEntity<ApiResponse<Map<String, Object>>> register(@Valid @RequestBody RegisterRequest req) {
String resolvedAppKey = resolveAppKey(req.appKey(), req.packageName(), req.licenseFile()); String resolvedAppKey = resolveAppKey(req.appKey(), req.licenseFile());
DeviceService.RegisterResult result = deviceService.register( DeviceService.RegisterResult result = deviceService.register(
resolvedAppKey, resolvedAppKey,
req.packageName(),
req.deviceId(), req.deviceId(),
req.deviceName(), req.deviceName(),
req.deviceModel(), req.deviceModel(),
req.deviceVendor(), req.deviceVendor(),
req.osVersion(), req.osVersion(),
req.userInfo()); req.userInfo());
Map<String, Object> data = new java.util.LinkedHashMap<>(); Map<String, Object> data = new LinkedHashMap<>();
data.put("success", result.success()); data.put("success", result.success());
data.put("token", result.token()); data.put("token", result.token());
if (result.message() != null) { if (result.message() != null) {
@ -50,9 +51,9 @@ public class LicensePublicController {
@PostMapping("/verify") @PostMapping("/verify")
public ResponseEntity<ApiResponse<Map<String, Object>>> verify(@Valid @RequestBody VerifyRequest req) { public ResponseEntity<ApiResponse<Map<String, Object>>> verify(@Valid @RequestBody VerifyRequest req) {
String resolvedAppKey = resolveAppKey(req.appKey(), req.packageName(), req.licenseFile()); String resolvedAppKey = resolveAppKey(req.appKey(), req.licenseFile());
DeviceService.VerifyResult result = deviceService.verify(resolvedAppKey, req.packageName(), req.deviceId(), req.token(), req.userInfo()); DeviceService.VerifyResult result = deviceService.verify(resolvedAppKey, req.deviceId(), req.token(), req.userInfo());
Map<String, Object> data = new java.util.LinkedHashMap<>(); Map<String, Object> data = new LinkedHashMap<>();
data.put("valid", result.valid()); data.put("valid", result.valid());
if (result.error() != null) { if (result.error() != null) {
data.put("error", result.error()); data.put("error", result.error());
@ -60,12 +61,28 @@ public class LicensePublicController {
return ResponseEntity.ok(ApiResponse.success(data)); return ResponseEntity.ok(ApiResponse.success(data));
} }
private static String resolveAppKey(String appKey, String packageName, String licenseFile) { /**
* Returns configured package names for the given appKey.
* Used by the SDK (appKey-only flow) to validate the caller's package name client-side.
*/
@GetMapping("/app-info")
public ResponseEntity<ApiResponse<Map<String, Object>>> appInfo(@RequestParam String appKey) {
AppLicenseEntity license;
try {
license = appLicenseService.getByAppKey(appKey);
} catch (BusinessException e) {
throw new BusinessException(404, "App not found");
}
Map<String, Object> data = new LinkedHashMap<>();
data.put("androidPackageName", license.getAndroidPackageName());
data.put("iosBundleId", license.getIosBundleId());
data.put("harmonyBundleName", license.getHarmonyBundleName());
return ResponseEntity.ok(ApiResponse.success(data));
}
private static String resolveAppKey(String appKey, String licenseFile) {
if (licenseFile != null && !licenseFile.isBlank()) { if (licenseFile != null && !licenseFile.isBlank()) {
LicenseFileCrypto.LicensePayload payload = LicenseFileCrypto.decrypt(licenseFile); LicenseFileCrypto.LicensePayload payload = LicenseFileCrypto.decrypt(licenseFile);
if (!payload.matchesPackageName(packageName)) {
throw new BusinessException(403, "包名与应用配置不匹配");
}
return payload.appKey(); return payload.appKey();
} }
if (appKey == null || appKey.isBlank()) { if (appKey == null || appKey.isBlank()) {
@ -76,7 +93,7 @@ public class LicensePublicController {
public record RegisterRequest( public record RegisterRequest(
String appKey, String appKey,
@NotBlank @JsonProperty("packageName") @JsonAlias("package_name") String packageName, @JsonProperty("packageName") @JsonAlias("package_name") String packageName,
@JsonProperty("licenseFile") @JsonAlias("license_file") String licenseFile, @JsonProperty("licenseFile") @JsonAlias("license_file") String licenseFile,
@NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId, @NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId,
@JsonProperty("deviceName") @JsonAlias("device_name") String deviceName, @JsonProperty("deviceName") @JsonAlias("device_name") String deviceName,
@ -88,7 +105,7 @@ public class LicensePublicController {
public record VerifyRequest( public record VerifyRequest(
String appKey, String appKey,
@NotBlank @JsonProperty("packageName") @JsonAlias("package_name") String packageName, @JsonProperty("packageName") @JsonAlias("package_name") String packageName,
@JsonProperty("licenseFile") @JsonAlias("license_file") String licenseFile, @JsonProperty("licenseFile") @JsonAlias("license_file") String licenseFile,
@NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId, @NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId,
@NotBlank String token, @NotBlank String token,

查看文件

@ -43,11 +43,9 @@ public class DeviceService {
} }
@Transactional @Transactional
public RegisterResult register(String appKey, String packageName, String deviceId, String deviceName, public RegisterResult register(String appKey, String deviceId, String deviceName,
String deviceModel, String deviceVendor, String osVersion, String deviceModel, String deviceVendor, String osVersion,
JsonNode userInfo) { JsonNode userInfo) {
validatePackageName(appKey, packageName);
// Check if device already registered for this app // Check if device already registered for this app
Optional<DeviceEntity> existingOpt = deviceRepository.findByAppKeyAndDeviceId(appKey, deviceId); Optional<DeviceEntity> existingOpt = deviceRepository.findByAppKeyAndDeviceId(appKey, deviceId);
if (existingOpt.isPresent()) { if (existingOpt.isPresent()) {
@ -116,12 +114,7 @@ public class DeviceService {
} }
@Transactional @Transactional
public VerifyResult verify(String appKey, String packageName, String deviceId, String token, JsonNode userInfo) { public VerifyResult verify(String appKey, String deviceId, String token, JsonNode userInfo) {
try {
validatePackageName(appKey, packageName);
} catch (BusinessException e) {
return new VerifyResult(false, e.getMessage());
}
if (!licenseAuthService.verifyTokenPayload(token, appKey, deviceId)) { if (!licenseAuthService.verifyTokenPayload(token, appKey, deviceId)) {
return new VerifyResult(false, "Token mismatch"); return new VerifyResult(false, "Token mismatch");
} }
@ -168,28 +161,6 @@ public class DeviceService {
appLicenseService.incrementRegisteredDevices(device.getAppKey()); appLicenseService.incrementRegisteredDevices(device.getAppKey());
} }
private void validatePackageName(String appKey, String packageName) {
if (packageName == null || packageName.isBlank()) {
throw new BusinessException(403, "packageName is required");
}
AppLicenseEntity license;
try {
license = appLicenseService.getByAppKey(appKey);
} catch (BusinessException e) {
throw new BusinessException(403, "App license not found");
}
String android = license.getAndroidPackageName();
String ios = license.getIosBundleId();
String harmony = license.getHarmonyBundleName();
boolean anyConfigured = hasText(android) || hasText(ios) || hasText(harmony);
if (anyConfigured) {
boolean matches = packageName.equals(android) || packageName.equals(ios) || packageName.equals(harmony);
if (!matches) {
throw new BusinessException(403, "包名与应用配置不匹配");
}
}
}
private static boolean hasText(String s) { private static boolean hasText(String s) {
return s != null && !s.isBlank(); return s != null && !s.isBlank();
} }