@Value讀取問題

在檔案讀取的方式上。EncryptionService 使用 Files.readAllBytes() 直接從檔案系統讀取,

而 Controller 使用 ClassPathResource 從 classpath 讀取。

Controller 的方式可以正常運作

@Slf4j
@Service
public class EncryptionService {

    @Value("${key.path.private:src/main/resources/private_key.pem}")
    private String privateKeyPath;

    private final RSAPrivateKey privateKey;

    public EncryptionService() throws Exception {
        // 初始化時載入私鑰
        this.privateKey = loadPrivateKey();
    }

    private RSAPrivateKey loadPrivateKey() throws Exception {
        try {
//            Resource resource = new ClassPathResource(privateKeyPath);
//            String privateKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
            // 讀取私鑰文件
            byte[] keyBytes = Files.readAllBytes(Paths.get(privateKeyPath));
            String privateKeyPEM = new String(keyBytes)
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s", "");
            log.info("privateKeyPEM:{}",privateKeyPEM);

            // 解碼 Base64 編碼的私鑰
            byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);

            // 轉換為 PKCS8 格式的私鑰
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
//        return null;
        } catch (Exception e) {
            log.error("Failed to load private key", e);
            throw new Exception("Failed to load private key: " + e.getMessage());
        }
    }
@RestController
@RequestMapping("/api")
public class PublicKeyController {

    private static final Logger logger = LoggerFactory.getLogger(PublicKeyController.class);

    @Value("${key.path.public:src/main/resources/public_key.pem}")
    private String publicKeyPath;


    @Value("${key.path.private:src/main/resources/private_key.pem}")
    private String privateKeyPath;

@GetMapping("/private-key")
public ResponseEntity<?> getPrivateKey() {
    try {
        // 使用 ClassPathResource 來讀取資源檔案
        Resource resource = new ClassPathResource(privateKeyPath);
        String publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        return ResponseEntity.ok(publicKey);

    } catch (FileNotFoundException e) {
        logger.error("Public key file not found: {}", privateKeyPath, e);
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body("Public key file not found");

    } catch (IOException e) {
        logger.error("Error reading public key file: {}", privateKeyPath, e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Error reading public key file");

    } catch (Exception e) {
        logger.error("Unexpected error while reading public key: {}", e.getMessage(), e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Internal server error");
    }
}
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.feddoubt.Cry.service.EncryptionService]: Constructor threw exception
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:223)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:88)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1310)
    ... 31 common frames omitted
Caused by: java.lang.Exception: Failed to load private key: null
    at com.feddoubt.Cry.service.EncryptionService.loadPrivateKey(EncryptionService.java:89)
    at com.feddoubt.Cry.service.EncryptionService.<init>(EncryptionService.java:31)
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:197)

修改 EncryptionService 採用相同的讀取方式。

    private RSAPrivateKey privateKey;

    @PostConstruct  // 改用 @PostConstruct 進行初始化
    public void init() throws Exception {
        this.privateKey = loadPrivateKey();
    }

改用 @PostConstruct 取代建構子初始化

使用 ClassPathResourceFileCopyUtils 來讀取檔案,這樣可以正確從 classpath 讀取資源

將 privateKey 欄位改為不用 final,因為我們改用 @PostConstruct 初始化