@Validated 기능 사용 시 Spring의 AOP Proxy 동과 관련되어 AService 클래스내에 @Autowired BService bService가 null 해결 방법

@Validated
@Service
class AService {
	@Autowired
    private BService bService;
    
    private Test getTest() {
    	// bService null인 상황일때
    }
}





@ActiveProfiles({ Application.ACTIVE_PROFILE_TEST_CASE })
@SpringBootTest
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Slf4j
class TestCase {
    @MockBean
    private BService bService;

    @Autowired
    private AService aService;

    @Test
    void test() {
        ...
        BService bService = new BService();
        Mockito.when(bService.get1("1")).thenReturn(new Test());
        ...

        ReflectionTestUtils.invokeMethod(AopTestUtils.getTargetObject(aService), AService.class, , "getTest");
    }
}

Class에 선언되지 않은 필드 정보가 넘어옴(columns 필드 선언되지 않음)

org.jboss.resteasy.spi.ReaderException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "columns"

...
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "columns"

무시하려면

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class .....

build.gradle

    implementation ('org.springframework.boot:spring-boot-starter-jdbc') {
        exclude group: 'com.zaxxer', module: 'HikariCP'
    }
    implementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.9.0'

application.yaml

 

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3380/testdb
    username: root
    password: test
    dbcp2:
      initial-size: 5
      max-total: 10
      test-while-idle: true
      time-between-eviction-runs-millis: 1000
      num-tests-per-eviction-run: 1

 

[Javascript]

// https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js
// https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/pbkdf2.js

var passphrase = "key...변경";
var iv = CryptoJS.lib.WordArray.random(128/8);
var key = CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32));
console.log("key: ", key)
var ct = CryptoJS.AES.encrypt(str, key, { iv: iv });
var enc = iv.concat(ct.ciphertext).toString();

console.log("enc: ", enc)
## enc:  b3ff8dd004bb643f0aba857baccb0d45e1565d2f4a1d727c9a268580d3a2031b

console.log("Result: " + CryptoJS.AES.decrypt({
        ciphertext: CryptoJS.enc.Hex.parse(enc.substring(32))
    }, CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32)),
    {
        iv: CryptoJS.enc.Hex.parse(enc.substring(0,32)),
    }).toString(CryptoJS.enc.Utf8));
    
## Result: abcd

enc = "B8160A9EDCA2CFECE3E6444BFDE09B780D8DD2D873B93080E7F5A6F4B5644217";
console.log("JAVA 에서 암호화한 문자열 복호화 Result: " + CryptoJS.AES.decrypt({
        ciphertext: CryptoJS.enc.Hex.parse(enc.substring(32))
    }, CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32)),
    {
        iv: CryptoJS.enc.Hex.parse(enc.substring(0,32)),
    }).toString(CryptoJS.enc.Utf8));
    
## JAVA 에서 암호화한 문자열 복호화 Result: abcd

[Java]

// com.google.guava:guava:29.0-jre


import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.AlgorithmParameters;
import java.util.Arrays;

public class AesCryptUtil {
    private static Logger logger = LoggerFactory.getLogger(AesCryptUtil.class);

    public static String encryptAES(String data, String secretKey) {
        try {
            byte[] secretKeys = Arrays.copyOfRange(Hashing.sha1().hashString(secretKey, Charsets.UTF_8).asBytes(), 0, 16);
            SecretKey secret = new SecretKeySpec(secretKeys, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = cipher.getParameters();

            byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
            byte[] cipherText = cipher.doFinal(data.getBytes(Charsets.UTF_8));

            return DatatypeConverter.printHexBinary(iv) + DatatypeConverter.printHexBinary(cipherText);
        } catch (Exception e) {
            logger.error("===== exception", e);
            throw new RuntimeException(e);
        }
    }

    public static String decryptAES(String data, String secretKey) {
        try {
            byte[] secretKeys = Arrays.copyOfRange(Hashing.sha1().hashString(secretKey, Charsets.UTF_8).asBytes(), 0, 16);

            String hexedIv = data.substring(0, 32);

            String hexedCipherText = data.substring(32);

            byte[] iv = DatatypeConverter.parseHexBinary(hexedIv);
            byte[] cipherText = DatatypeConverter.parseHexBinary(hexedCipherText);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKeys, "AES"), new IvParameterSpec(iv));

            return new String(cipher.doFinal(cipherText), Charsets.UTF_8);
        }catch (Exception e) {
            logger.error("===== exception", e);
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        String key = "key...변경";


        String abcd = AesCryptUtil.encryptAES("abcd", key);
        System.out.println("enc: " + abcd);
        System.out.println("dec: " + AesCryptUtil.decryptAES(abcd, key));
        System.out.println("javascript 암호화한 문자열 복호화 dec: " + AesCryptUtil.decryptAES("b3ff8dd004bb643f0aba857baccb0d45e1565d2f4a1d727c9a268580d3a2031b", key));
    }
}

[참고] https://stackoverflow.com/questions/37368710/decrypt-aes-cbc-pkcs5padding-with-cryptojs

 

Decrypt AES/CBC/PKCS5Padding with CryptoJS

I generate 128bit AES/CBC/PKCS5Padding key using Java javax.crypto API. Here is the algorithm that I use: public static String encryptAES(String data, String secretKey) { try { byte[]

stackoverflow.com

 

 

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Test {
    public static void main(String[] args) {
        String hexString = "123 abc [0xea][0xb0][0x80][0xeb][0x82][0x98] [0xeb][0x8b][0xa4][0xeb][0x9d][0xbc]";
        do {
            hexString = hexToString(hexString); // output : 123 abc 가나 다라
        } while (hexString.indexOf("[0x") > -1);
        System.out.println("en : " + hexString);
    }
    
    private static String hexToString(String s) {
        try {
            int idx = s.indexOf("[0x");
            if (idx > -1) {
                String t = s.substring(idx, idx + 18);
                String c = new String(Hex.decodeHex(t.replaceAll("\\[0x", "").replaceAll("\\]", "").toCharArray()));
                s = s.replace(t, c);
            }
        } catch (DecoderException e) {
            throw new RuntimeException(e);
        }

        return s;
    }
}

방법

  1. HttpClient 등 모듈에서 유효한 인증서를 체크하는 로직을 예외 시키는 방법
  2. certificate 인증서를 저장하여 호출하는 서버의 keystore 에 등록해주는 방법

방법 1에 대한 방법

            ## HttpClient 4.x 
            
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, (certificate, authType) -> true);

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    builder.build(),
                    NoopHostnameVerifier.INSTANCE);

            httpClientBuilder.setSSLSocketFactory(sslsf);

방법 2에 대한 방법

  1. 크롬에서 아래와 같이 test1.cer 파일로 저장 (이름 무관)
  2. keytool -import -alias test1 -keystore $JAVA_HOME/lib/security/cacerts -file test1.cer -storepass changeit (alias 기존과 겹치지 않는 적당한 명칭, pass는 적당하게)
    1. 해당 파일만 가져와서 하드코딩으로 지정 할때
      System.setProperty("javax.net.ssl.trustStore", "$JAVA_HOME/lib/security/cacerts");

2024현재

 

과거

 

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
	at UrlConTest.main(UrlConTest.java:15)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
	at sun.security.validator.Validator.validate(Validator.java:262)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
	... 14 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
	... 20 more

 

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class TextTest {
	public static void main(String[] args) {
		Path path1 = Paths.get("test.log");
		Path path2 = Paths.get("test.log-2");

		try (Stream stream = Files.lines(path1)) {
			Files.write(path2, (Iterable)stream.filter(s->s.trim().startsWith("{")).filter(s->s.trim().endsWith("}"))::iterator);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


json-lib, com.fasterxml.jackson 둘의 차이점을 비교해본다.

별다른 옵션 없는 심플한 상황이다.

json-lib의 경우 Object형의 NULL 변환에 문제가 있어보인다.


[JsonTest .java]

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

public class JsonTest {
	@Test
	public void test_jsonlib_BeanCase() {
		try {
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			
			JSONObject jsonObj = JSONObject.fromObject( JSONSerializer.toJSON(inBean) );
			String jsonStr = jsonObj.toString();
			System.out.println("jsonStr: " + jsonStr);
			
			jsonObj = (JSONObject) JSONObject.fromObject(jsonStr);
			TestBean outBean = (TestBean) JSONObject.toBean(jsonObj, TestBean.class);
			System.out.println(outBean);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			//assertEquals(inBean.getC(), outBean.getC());				// 문제 소지
			//assertEquals(inBean.getD(), outBean.getD());				// 문제 소지
			
			assertEquals(outBean.getC(), "");
			assertEquals(outBean.getD(), "");
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			//assertEquals(inBean.getInt21(), outBean.getInt21());		// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getInt22(), outBean.getInt22());		// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getInt21(), new Integer(0));
			assertEquals(outBean.getInt22(), new Integer(0));
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			//assertEquals(inBean.getBool21(), outBean.getBool21());	// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getBool22(), outBean.getBool22());	// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getBool21(), new Boolean(false));
			assertEquals(outBean.getBool22(), new Boolean(false));
		} catch (Exception e) {
			System.err.println("-- e: " + e);
		}
		
	}
	
	@Test
	public void test_jackson_BeanCase() {
		try {
			ObjectMapper jacksonMapper = new ObjectMapper();
			
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			System.out.println(inBean);
			
			String jsonStr = jacksonMapper.writeValueAsString(inBean);
			System.out.println("jsonStr: " + jsonStr);
			TestBean outBean = jacksonMapper.readValue(jsonStr, TestBean.class);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			assertEquals(inBean.getC(), outBean.getC());
			assertEquals(inBean.getD(), outBean.getD());
			assertEquals(outBean.getC(), null);
			assertEquals(outBean.getD(), null);
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			assertEquals(inBean.getInt21(), outBean.getInt21());
			assertEquals(inBean.getInt22(), outBean.getInt22());
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			assertEquals(inBean.getBool21(), outBean.getBool21());
			assertEquals(inBean.getBool22(), outBean.getBool22());
		} catch (IOException e) {
			System.err.println("-- e: " + e);
		}
	}
}

[TestBean.java]

import lombok.Getter;
import lombok.Setter;

@Setter @Getter
public class TestBean {
	String a;
	String b;
	String c;
	String d;
	
	int int11;
	int int12;
	Integer int21;
	Integer int22;
	
	boolean bool11;
	boolean bool12;
	Boolean bool21;
	Boolean bool22;
}

[build.gradle]

sourceCompatibility='1.7'

dependencies {
    //compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    //compile 'org.apache.maven.plugins:maven-war-plugin:+'
    
    compile 'net.sf.json-lib:json-lib:2.4:jdk15@jar'
    compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.6.4'
    
    compile 'commons-lang:commons-lang:2.6'
    compile 'commons-logging:commons-logging:1.2'
    compile 'commons-collections:commons-collections:3.2.2'
    compile 'commons-beanutils:commons-beanutils:1.9.2'
    compile 'net.sf.ezmorph:ezmorph:1.0.6'
    
    testCompile 'junit:junit:4.11'
	testCompile 'org.hamcrest:hamcrest-all:1.3'
}


import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.StringUtils;

// 자연수 + 공백
assertFalse(StringUtils.isNumeric(null));
assertTrue (StringUtils.isNumeric(""));
assertFalse(StringUtils.isNumeric("  "));
assertTrue (StringUtils.isNumeric("0"));
assertTrue (StringUtils.isNumeric("123"));
assertFalse(StringUtils.isNumeric("12 3"));
assertFalse(StringUtils.isNumeric("ab2c"));
assertFalse(StringUtils.isNumeric("12-3"));
assertFalse(StringUtils.isNumeric("12.3"));
assertFalse(StringUtils.isNumeric("-123"));
assertFalse(StringUtils.isNumeric("+123"));

// 자연수
assertFalse(NumberUtils.isDigits(null));
assertFalse(NumberUtils.isDigits(""));
assertFalse(NumberUtils.isDigits("  "));
assertTrue (NumberUtils.isDigits("0"));
assertTrue (NumberUtils.isDigits("123"));
assertFalse(NumberUtils.isDigits("12 3"));
assertFalse(NumberUtils.isDigits("ab2c"));
assertFalse(NumberUtils.isDigits("12-3"));
assertFalse(NumberUtils.isDigits("12.3"));
assertFalse(NumberUtils.isDigits("-123"));
assertFalse(NumberUtils.isDigits("+123"));

// 정수 + 실수 (+ 앞에 붙으면 안됨)
assertFalse(NumberUtils.isNumber(null));
assertFalse(NumberUtils.isNumber(""));
assertFalse(NumberUtils.isNumber("  "));
assertTrue (NumberUtils.isNumber("0"));
assertTrue (NumberUtils.isNumber("123"));
assertFalse(NumberUtils.isNumber("12 3"));
assertFalse(NumberUtils.isNumber("ab2c"));
assertFalse(NumberUtils.isNumber("12-3"));
assertTrue (NumberUtils.isNumber("12.3"));
assertTrue (NumberUtils.isNumber("-123"));
assertFalse(NumberUtils.isNumber("+123"));
	
		...
		
		...
	

	...

	
		
	
	
	
		
        
	

	

	
		
			
			
		
	

	...

+ Recent posts