REST API 형식의 AES256 과제를 수행 중 발생한 이슈들
단순히 Request URL 파라미터를 던질 시 발생하는 인코딩 이슈
인코딩 이슈가 발생하는 기본적인 인코딩 방법에 대해서 알아보고 발생한 이슈를 해결한 방법을 알아봄
기본적으로 웹 브라우저/클라이언트에서 웹 서버로 단순한 GET 요청이 아닌 POST의 경우 이에 대한 파라미터(Request Parameter)를 보내고 해석하기 위해선 브라우저와 서버가 동일한 charset 인코딩 방법을 사용해야한다.
따라서 jsp에서는 <%@ page contentType="text/html; charset="EUC-KR" %>
와 같은 방식으로 jsp가 응답 할(서버가 렌더링 할) charset을 명시하고, 브라우저는 이에 맞춘 charset으로 데이터를 전송한다.
또한, 별도로 각 Http request/response 헤더에 타입을 명시하여 인코딩한다.
URL Obejct의 사용
URL Object
URL Object
String URL UTF-8 인코딩/디코딩
org.springframework.web.util.DefaultUriTemplateHandler
을 사용해서 자동 인코딩org.springframework.http.converter.StringHttpMessageConverter
을 사용하여 자동 인코딩ResponseEntity.xxForEntity(accessURL, class)
에 의해 UTF-8로 재인코딩이 되지 않도록 URI 객체를 넘겨야한다.별도의 인코딩을 적용하지 않았음에도 자동 인코딩이 적용되어 정상 통신
public static void MyrestTemplatePost() throws NoSuchElementException, UnsupportedEncodingException, GeneralSecurityException {
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Header 설정
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON); // RequestBody를 통해 서버가 받을 MIME
httpHeaders.setAccept(Collections.singletonList(MediaType.valueOf("application/json; charset=UTF-8"))); // ResponseBody를 통해 내가 받을 MIME
/*
* HttpEntity 설정
*/
HttpEntity<JSONObject> httpEntity = new HttpEntity<JSONObject>(jsonObject, httpHeaders);
System.out.println("Client Send Entity" + httpEntity);
/*
* ResponseEntity 설정
* accessURL, Request Object, Response Type, HTTP Header
*/
ResponseEntity<String> responseEntity = restTemplate.postForEntity(apiURL, httpEntity, String.class, httpHeaders);
System.out.println("Server Response Entity: " + responseEntity.toString());
}
@RequestMapping(value = "rest", method = RequestMethod.POST)
@ResponseBody
public static ResponseEntity<String> restTemplatePost(@RequestBody JSONObject jsonObject) throws NoSuchElementException,
UnsupportedEncodingException, GeneralSecurityException, JSONException {
ResponseEntity<String> responseEntity = null;
System.out.println("Client Data: " + jsonObject);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=UTF-8");
responseEntity = new ResponseEntity<String>(jsonObject.toJSONString(), responseHeaders, HttpStatus.OK);
System.out.println("Server Respon Entity: " + responseEntity);
return responseEntity;
}
클라이언트
Client Send Entity:
<{"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="},{Content-Type=[application/json], Accept=[application/json;charset=UTF-8]}>
Server Response Entity:
<200 OK,{"phoneNo":"0123456789","resultMSG":"SUCCESS","type":"type1","socialNo":"1234567","resultCD":"0000"},{Server=[Apache-Coyote/1.1], Content-Type=[application/json;charset=UTF-8], Content-Length=[100], Date=[Mon, 10 Jan 2022 02:15:32 GMT]}>
서버
Client Send Data: {"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="}
Server Respon Entity:
<200 OK,{"phoneNo":"0123456789","resultMSG":"SUCCESS","type":"type1","socialNo":"1234567","resultCD":"0000"},{Content-Type=[application/json; charset=UTF-8]}>
SEND JSON: {"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="}
URL Encode: %7B%22phoneNo%22%3A%22OAP1jPHVcsiZIcz%5C%2Ff5YARg%3D%3D%22%2C%22type%22%3A%22type1%22%2C%22socialNo%22%3A%22wU6VVLaZN0Moq%2B4nOyVEVw%3D%3D%22%7D
RESPONSE JSON: {"resultMsg":"success","phoneNo":"0123456789","type":"type1","socialNo":"1234567","resultCD":"0000"}
threw exception [Request processing failed;
nested exception is Unexpected character (=)
at position 92.] with root cause
Unexpected character (=) at position 92.
Client Http Conn Data:
%7B%22phoneNo%22%3A%22OAP1jPHVcsiZIcz%5C%2Ff5YARg%3D%3D%22%2C%22type%22%3A%22type1%22%2C%22socialNo%22%3A%22wU6VVLaZN0Moq%2B4nOyVEVw%3D%3D%22%7D=
URL Decode String: {"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="}
RESPONSE JSON: {"resultMsg":"success","phoneNo":"0123456789","type":"type1","socialNo":"1234567","resultCD":"0000"}
클라이언트가 보낸 데이터{"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="}
서버가 받은 데이터
%7B%22phoneNo%22%3A%22OAP1jPHVcsiZIcz%5C%2Ff5YARg=%3D%22%2C%22type%22%3A%22type1%22%2C%22socialNo%22%3A%22wU6VVLaZN0Moq+4nOyVEVw%3D%3D%22%7D
디코딩 후
{"phoneNo":"OAP1jPHVcsiZIcz\/f5YARg==","type":"type1","socialNo":"wU6VVLaZN0Moq 4nOyVEVw=="}
POST 데이터 파라미터 인코딩의 경우 '+'가 공백을 의미하기에 공백 치환 후 공백을 암호화할 수 없음. 공백 암호화 예외 발생
ception: Given final block not properly padded] with root cause
javax.crypto.BadPaddingException: Given final block not properly padded
따라서 URL 인코딩을 사용하기 위한 java.lang.urlEncoder.encode()/decode()에는 다음과 같은 로직이 포함
public static String encode(String s, String enc)
throws UnsupportedEncodingException {
for (int i = 0; i < s.length();) {
int c = (int) s.charAt(i);;
if (dontNeedEncoding.get(c)) {
if (c == ' ') {
c = '+';
needToChange = true;
}
public static String decode(String s, String enc)
throws UnsupportedEncodingException{
char c;
byte[] bytes = null;
while (i < numChars) {
c = s.charAt(i);
switch (c) {
case '+':
sb.append(' ');
원래 있던 '+' 문자의 경우는?
URLEncode.encode에 의해 '%2B'로 ASCII 변환
"socialNo":"wU6VVLaZN0Moq+4nOyVEVw=="}
socialNo%22%3A%22wU6VVLaZN0Moq%2B4nOyVEVw%3D%3D%22%7D
RequestMapping을 위해선 클라이언트가 전송한 데이터를 자바 Object로 바인딩시켜야하는데 그렇지 못했을 때 발생
[0][type=org.springframework.validation.support.BindingAwareModuleMap] [value={}]
RequestMapping 메소드에 @RequestBody 어노테이션 적용
@RequestBody는 Client가 전송하는 Json(application/json) 형태의 HTTP Body 내용을 Java Object로 Binding 시켜주는 역할
@RequestBody로 받는 Data는 Spring에서 관리하는 MessageConverter들 중 하나인 MappingJackson2HttpMessageConverte를 통해 Java Object로 Binding
@RequestBody는 Setter함수가 없어도 요청받은 데이터를 Binding 가능