설정 기반 EncField 암복호화 필터 추가
- 기관마다 경로만 다른 필터 클래스를 만들지 않도록 경로를 PropManager 에서 읽는 공통 필터 추가
ConfigurableEncFieldInCryptoFilter(수신), ConfigurableEncFieldOutCryptoFilter(송신)
- PropManager 그룹 CryptoFilter, 키 {어댑터그룹명}.CRYPTO_DEC_FROM_PATH / DEC_TO_PATH / ENC_FROM_PATH / ENC_TO_PATH
- 설정이 없으면 기존 EncField 필터와 같은 경로(/encryptedData, /, /, /encryptedData)
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.adapter.http.client.impl.HttpClient5AdapterServiceRest;
|
||||
import com.eactive.eai.adapter.http.filter.CryptoFilterPathConfig;
|
||||
import com.eactive.eai.common.util.JacksonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* 어댑터그룹별 암복호화 JSON 경로를 PropManager "CryptoFilter" 그룹에서 읽는 송신 암복호화 필터.
|
||||
*
|
||||
* 외부기관마다 경로만 다른 필터 클래스를 만드는 대신 이 필터 + 프로퍼티 설정으로 대체한다.
|
||||
* 경로 키와 기본값은 {@link CryptoFilterPathConfig} 참조.
|
||||
* OutCryptoFilter 는 경로를 tempProp 에서 읽으므로 경로는 tempProp 에 반영한다.
|
||||
*
|
||||
* doPreFilter : 송신 요청 암호화. HEADER_GROUP 으로 지정된 헤더 그룹은 암호화 대상에서 빼고 암호화 후 다시 붙인다.
|
||||
* doPostFilter : 수신 응답 복호화
|
||||
*
|
||||
* 필터 타입 등록 (FQCN):
|
||||
* com.eactive.eai.adapter.http.client.impl.filter.ConfigurableEncFieldOutCryptoFilter
|
||||
*/
|
||||
public class ConfigurableEncFieldOutCryptoFilter implements HttpClientAdapterFilter {
|
||||
|
||||
private final OutCryptoFilter filter = new OutCryptoFilter();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
String headerGroupName = prop.getProperty(HttpClient5AdapterServiceRest.HEADER_GROUP);
|
||||
|
||||
ObjectNode root = (ObjectNode) JacksonUtil.readTree(message);
|
||||
JsonNode header = null;
|
||||
if (headerGroupName != null) {
|
||||
header = root.get(headerGroupName);
|
||||
root.remove(headerGroupName);
|
||||
}
|
||||
|
||||
Object enc = filter.doPreFilter(adptGrpName, adptName, prop, root,
|
||||
CryptoFilterPathConfig.apply(adptGrpName, tempProp));
|
||||
ObjectNode encJson = (ObjectNode) JacksonUtil.readTree(enc);
|
||||
if (header != null) {
|
||||
encJson.set(headerGroupName, header);
|
||||
}
|
||||
return encJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return filter.doPostFilter(adptGrpName, adptName, prop, message,
|
||||
CryptoFilterPathConfig.apply(adptGrpName, tempProp));
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceRest;
|
||||
import com.eactive.eai.adapter.http.filter.CryptoFilterPathConfig;
|
||||
import com.eactive.eai.common.util.JacksonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* 어댑터그룹별 암복호화 JSON 경로를 PropManager "CryptoFilter" 그룹에서 읽는 수신 암복호화 필터.
|
||||
*
|
||||
* 외부기관마다 경로만 다른 필터 클래스를 만드는 대신 이 필터 + 프로퍼티 설정으로 대체한다.
|
||||
* 경로 키와 기본값은 {@link CryptoFilterPathConfig} 참조.
|
||||
*
|
||||
* doPreFilter : 수신 요청 복호화
|
||||
* doPostFilter : 송신 응답 암호화. HEADER_GROUP 으로 지정된 헤더 그룹은 암호화 대상에서 빼고 암호화 후 다시 붙인다.
|
||||
*
|
||||
* 필터 타입 등록 (FQCN):
|
||||
* com.eactive.eai.adapter.http.dynamic.filter.ConfigurableEncFieldInCryptoFilter
|
||||
*/
|
||||
public class ConfigurableEncFieldInCryptoFilter implements HttpAdapterFilter {
|
||||
|
||||
private final InCryptoFilter filter = new InCryptoFilter();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return filter.doPreFilter(adptGrpName, adptName, message, CryptoFilterPathConfig.apply(adptGrpName, prop),
|
||||
request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
try {
|
||||
ObjectNode root = (ObjectNode) JacksonUtil.readTree(resultMessage);
|
||||
String headerGroupName = prop.getProperty(HttpAdapterServiceRest.HEADER_GROUP);
|
||||
JsonNode header = null;
|
||||
if (headerGroupName != null) {
|
||||
header = root.get(headerGroupName);
|
||||
root.remove(headerGroupName);
|
||||
}
|
||||
|
||||
Object enc = filter.doPostFilter(adptGrpName, adptName, root,
|
||||
CryptoFilterPathConfig.apply(adptGrpName, prop), request, response);
|
||||
ObjectNode encJson = (ObjectNode) JacksonUtil.readTree(enc);
|
||||
if (header != null) {
|
||||
encJson.set(headerGroupName, header);
|
||||
}
|
||||
return encJson;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new FilterCryptoException("암호화 오류", InCryptoFilter.ERROR_ENC_FAIL,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.eactive.eai.adapter.http.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
|
||||
/**
|
||||
* 설정 기반 암복호화 필터의 JSON 경로를 어댑터그룹별로 PropManager 에서 읽어 반영한다.
|
||||
*
|
||||
* 프로퍼티 그룹 "CryptoFilter", 키는 "{어댑터그룹명}.{CRYPTO_*_PATH}".
|
||||
*
|
||||
* <pre>
|
||||
* {어댑터그룹명}.CRYPTO_DEC_FROM_PATH = /encryptedData (기본값) 복호화할 암호문 필드
|
||||
* {어댑터그룹명}.CRYPTO_DEC_TO_PATH = / (기본값) 복호화 결과 반영 위치. "/" 면 암호문 필드를 지우고 body 에 병합
|
||||
* {어댑터그룹명}.CRYPTO_ENC_FROM_PATH = / (기본값) 암호화 대상. "/" 면 body 전체
|
||||
* {어댑터그룹명}.CRYPTO_ENC_TO_PATH = /encryptedData (기본값) 암호문을 넣을 필드
|
||||
* </pre>
|
||||
*
|
||||
* 필터 인스턴스는 팩토리에 캐시되어 모든 어댑터그룹이 공유하므로 설정은 호출마다 읽는다.
|
||||
*
|
||||
* @see com.eactive.eai.adapter.http.dynamic.filter.ConfigurableEncFieldInCryptoFilter
|
||||
* @see com.eactive.eai.adapter.http.client.impl.filter.ConfigurableEncFieldOutCryptoFilter
|
||||
*/
|
||||
public final class CryptoFilterPathConfig {
|
||||
|
||||
public static final String PROP_GROUP = "CryptoFilter";
|
||||
|
||||
static final String DEFAULT_DEC_FROM_PATH = "/encryptedData";
|
||||
static final String DEFAULT_DEC_TO_PATH = AbstractCryptoFilter.PATH_ROOT;
|
||||
static final String DEFAULT_ENC_FROM_PATH = AbstractCryptoFilter.PATH_ROOT;
|
||||
static final String DEFAULT_ENC_TO_PATH = "/encryptedData";
|
||||
|
||||
private static final String[] PATH_KEYS = { AbstractCryptoFilter.PROP_DEC_FROM_PATH,
|
||||
AbstractCryptoFilter.PROP_DEC_TO_PATH, AbstractCryptoFilter.PROP_ENC_FROM_PATH,
|
||||
AbstractCryptoFilter.PROP_ENC_TO_PATH };
|
||||
|
||||
private CryptoFilterPathConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* target 에 경로 프로퍼티 4개를 반영한다. (수신 필터는 prop, 송신 필터는 tempProp)
|
||||
* 기본값을 먼저 넣고 CryptoFilter 그룹에 설정된 값으로 덮어쓴다. 값이 비어 있는 키는 기본값을 유지한다.
|
||||
*
|
||||
* @param adptGrpName 어댑터그룹명
|
||||
* @param target 경로를 반영할 Properties
|
||||
* @return target
|
||||
*/
|
||||
public static Properties apply(String adptGrpName, Properties target) {
|
||||
target.setProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH, DEFAULT_DEC_FROM_PATH);
|
||||
target.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, DEFAULT_DEC_TO_PATH);
|
||||
target.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, DEFAULT_ENC_FROM_PATH);
|
||||
target.setProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH, DEFAULT_ENC_TO_PATH);
|
||||
|
||||
// 그룹이 없을 때 getProperties() 는 예외를 던지므로 먼저 확인한다.
|
||||
PropManager propManager = PropManager.getInstance();
|
||||
if (StringUtils.isBlank(adptGrpName) || !propManager.isContainProperties(PROP_GROUP)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
for (String key : PATH_KEYS) {
|
||||
String value = propManager.getProperty(PROP_GROUP, adptGrpName + "." + key, null);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
target.setProperty(key, value.trim());
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.eactive.eai.adapter.http.filter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* CryptoFilterPathConfig 단위테스트
|
||||
*
|
||||
* PropManager 는 ApplicationContextProvider 의 context 에 mock 을 리플렉션으로 주입해 격리한다.
|
||||
*/
|
||||
class CryptoFilterPathConfigTest {
|
||||
|
||||
private static final String GROUP = "_TST_IN_RST_SyS";
|
||||
|
||||
private PropManager propManager;
|
||||
|
||||
private Field contextField;
|
||||
|
||||
private Object previousContext;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
propManager = Mockito.mock(PropManager.class);
|
||||
|
||||
ApplicationContext ctx = Mockito.mock(ApplicationContext.class);
|
||||
Mockito.when(ctx.getBean(PropManager.class)).thenReturn(propManager);
|
||||
|
||||
contextField = ApplicationContextProvider.class.getDeclaredField("context");
|
||||
contextField.setAccessible(true);
|
||||
previousContext = contextField.get(null);
|
||||
contextField.set(null, ctx);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
contextField.set(null, previousContext);
|
||||
}
|
||||
|
||||
/** CryptoFilter 그룹이 있고 어댑터그룹의 key 에 value 가 설정된 상태로 만든다. */
|
||||
private void configure(String adapterGroupName, String key, String value) {
|
||||
Mockito.when(propManager.isContainProperties(CryptoFilterPathConfig.PROP_GROUP)).thenReturn(true);
|
||||
Mockito.when(propManager.getProperty(CryptoFilterPathConfig.PROP_GROUP, adapterGroupName + "." + key, null))
|
||||
.thenReturn(value);
|
||||
}
|
||||
|
||||
private void assertDefaults(Properties p) {
|
||||
assertEquals("/encryptedData", p.getProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH));
|
||||
assertEquals("/", p.getProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH));
|
||||
assertEquals("/", p.getProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH));
|
||||
assertEquals("/encryptedData", p.getProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1. CryptoFilter 그룹이 없으면 4개 경로 모두 기본값")
|
||||
void 그룹없음_기본값() {
|
||||
Properties p = new Properties();
|
||||
|
||||
Properties result = CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertSame(p, result);
|
||||
assertDefaults(p);
|
||||
Mockito.verify(propManager, Mockito.never()).getProperty(anyString(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2. 그룹은 있지만 어댑터그룹 키가 없으면 기본값")
|
||||
void 키없음_기본값() {
|
||||
Mockito.when(propManager.isContainProperties(CryptoFilterPathConfig.PROP_GROUP)).thenReturn(true);
|
||||
Properties p = new Properties();
|
||||
|
||||
CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertDefaults(p);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3. 4개 키가 모두 설정되면 모두 반영")
|
||||
void 네개키_반영() {
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_DEC_FROM_PATH, "/encrypted_data");
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_DEC_TO_PATH, "/data");
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_ENC_FROM_PATH, "/data");
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_ENC_TO_PATH, "/encrypted_data");
|
||||
Properties p = new Properties();
|
||||
|
||||
CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertEquals("/encrypted_data", p.getProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH));
|
||||
assertEquals("/data", p.getProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH));
|
||||
assertEquals("/data", p.getProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH));
|
||||
assertEquals("/encrypted_data", p.getProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("4. 빈 값은 무시하고 기본값 유지, 앞뒤 공백은 제거")
|
||||
void 빈값무시_공백제거() {
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_DEC_FROM_PATH, " ");
|
||||
configure(GROUP, AbstractCryptoFilter.PROP_ENC_TO_PATH, " /encrypted_data ");
|
||||
Properties p = new Properties();
|
||||
|
||||
CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertEquals("/encryptedData", p.getProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH));
|
||||
assertEquals("/encrypted_data", p.getProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("5. 다른 어댑터그룹의 키는 반영하지 않는다")
|
||||
void 다른그룹_미반영() {
|
||||
configure("_OTHER_IN_RST_SyS", AbstractCryptoFilter.PROP_DEC_FROM_PATH, "/encrypted_data");
|
||||
Properties p = new Properties();
|
||||
|
||||
CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertDefaults(p);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("6. 어댑터그룹명이 없으면 PropManager 를 조회하지 않고 기본값")
|
||||
void 그룹명없음_기본값() {
|
||||
Mockito.when(propManager.isContainProperties(CryptoFilterPathConfig.PROP_GROUP)).thenReturn(true);
|
||||
Properties p = new Properties();
|
||||
|
||||
CryptoFilterPathConfig.apply(null, p);
|
||||
|
||||
assertDefaults(p);
|
||||
Mockito.verify(propManager, Mockito.never()).getProperty(anyString(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("7. 앞 단계에서 들어온 경로 값은 기본값/설정값으로 덮어쓴다")
|
||||
void 기존값_덮어씀() {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, "/leftover");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, "/leftover");
|
||||
|
||||
CryptoFilterPathConfig.apply(GROUP, p);
|
||||
|
||||
assertDefaults(p);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user