숫자형의 길이 구하기.

|

String a = "abced";
System.out.println("길이>>"+a.length);
  • 위와 같은 코드에서는 아래의 결과가 나올것이다.

길이: 5

  • String 형의 경우 length 함수를 지원하지만, int형은 길이 함수가 없다. 이럴경우 Math 함수를 사용하면 자리수를 구할 수 있다.
int a = 3291;
int length = (int)(Math.log10(a)+1);
System.out.println("길이 : " + a);

길이 : 4

[2026년 추가] 이 방식은 부동소수점 오차 때문에 10의 거듭제곱수(10, 100, 1000…)에서 가끔 틀린 값을 낼 수 있다고 알려져 있다. Math.log10(1000)이 이상적으로는 정확히 3.0이어야 하지만, 부동소수점 연산 특성상 환경에 따라 2.9999999999999996 같은 값이 나올 수 있고, 이 경우 +1 후 (int)로 잘라내는 과정에서 자릿수가 1 작게 나온다. 자릿수 세는 게 목적이라면 부동소수점 연산을 아예 쓰지 않는 다음 방법이 더 안전하다.

int length = String.valueOf(Math.abs(a)).length(); // 음수는 절대값으로 처리

Spring boot 환경에서 Amazon s3에 이미지파일 업로드하기.

|

[2026년 추가] 이 글에서 쓴 com.amazonaws:aws-java-sdk(AWS SDK for Java v1)는 2024년 7월 31일부로 유지보수 모드로 전환됐고, 2025년 12월 31일부로 공식 지원이 종료됐다 (기존 앱이 당장 멈추진 않지만, 신규 기능/보안 패치를 더 받지 못한다). 새로 프로젝트를 시작한다면 software.amazon.awssdk:s3(SDK v2)를 쓰는 게 맞다. v2에서는 패키지가 software.amazon.awssdk.*로 바뀌고, AmazonS3ClientBuilder 대신 S3Client.builder()...build() 형태로 빌더 API 이름도 조금씩 달라졌다 — 흐름 자체(자격증명 → 리전 → 클라이언트 빌드)는 아래 예제와 동일하니 마이그레이션 가이드를 참고하면 어렵지 않다.

amazon s3 인증 받기 및 설정 관련 내용은 생략하였다.

프로젝트 구성을 위해 의존성을 추가.

  • pom.xml
....
<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk</artifactId>
	<version>1.11.313</version>
</dependency>
....
  • application.yml 설정파일.
spring
 aws:
  access-key: 발급받은 accessKey
  secret-key: 발급받은 secretKey
  region: 지역
  bucket: s3 버킷이름
  url: S3 url
  • AWS properties Configuration 추가.
@Configuration
@ConfigurationProperties(prefix="spring.aws")
public static class AwsProperties{
	private String accessKey;
	private String secretKey;
	private String region;
	private String bucket;
	private String url;

	//<editor-fold desc="getter setter">
	public void setAccessKey(String accessKey){
		this.accessKey=accessKey;
	}

	public void setSecretKey(String secretKey){
		this.secretKey=secretKey;
	}

	public void setRegion(String region){
		this.region=region;
	}

	public String getBucket(){
		return bucket;
	}

	public void setBucket(String bucket){
		this.bucket=bucket;
	}

	public String getUrl(){
		return url;
	}

	public void setUrl(String url){
		this.url=url;
	}
}

  • AmazonS3Client 빈 등록
      
    import com.amazonaws.auth.AWSStaticCredentialsProvider;
    import com.amazonaws.auth.BasicAWSCredentials;
    import com.amazonaws.regions.Regions;
    import com.amazonaws.services.s3.AmazonS3;
    import com.amazonaws.services.s3.AmazonS3ClientBuilder;
    .....  
    @Bean
    public AmazonS3 amazonS3Client(AwsProperties awsProperties){
    	return AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(awsProperties.accessKey, awsProperties.secretKey))).withRegion(Regions.fromName(awsProperties.region)).build();
    }
    ...
    
  • Controller에서 이미지 업로드 호출 부분.

@Controller
public class UploadController {

	@Autowired 
	private UploadService uploadService
	....
	@PostMapping("/uploadImage")
	public void background(
			@RequestParam("userImage") MultipartFile multipartFile
	){
		String uploadUrl = "업로드 url 정보";
		uploadService.updateBackgroundImage(uploadUrl, multipartFile);
	}
	....
	
}
  • Service 에서 AmazonS3 에 이미지 업로드 하는 부분.

@Service
public class UploadService {

	@Autowired
	private AmazonS3 amazonS3;
	
	@Autowired
	private AwsProperties awsProperties;
	
	....
	// controller 에서 실행한 부분.
	public void updateBackgroundImage(String url, MultipartFile multipartFile){
        upload(url, multipartFile);
    }
	
	private void upload(String url, MultipartFile file){
        try{
            String uploadFileName = file.getOriginalFilename();
            String fileExtension = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1, uploadFileName.length());

            String contentTypeTail = "jpeg";
            if (fileExtension.toLowerCase().equals("gif")) contentTypeTail = "gif";
            else if (fileExtension.toLowerCase().equals("png")) contentTypeTail = "png";

            byte[] bytes = file.getBytes();
            upload(url, new ByteArrayInputStream(bytes), contentTypeTail);
        }catch(Throwable t){
            throw new JmlimException(t.getMessage(), HttpStatus.BAD_REQUEST, t);
        }
    }
	
	private void upload(String key, InputStream inputStream, String imageType){
        ObjectMetadata metadata = new ObjectMetadata();
        if(imageType != null) {
            metadata.addUserMetadata("Content-Type", "image/"+imageType);
        }

        PutObjectRequest putObjectRequest = new PutObjectRequest(awsProperties.getBucket(), key, inputStream, metadata);
		// 퍼블릭으로 공개하여 올림.
        putObjectRequest.setCannedAcl(CannedAccessControlList.PublicRead);
        amazonS3.putObject(putObjectRequest);
    }
}

Java AES256 Sample 코드.

|

AES 란?

Advanced Encryption Standard를 줄인 말이다. 한국어로 번역하면 ‘고급 암호화 표준’이다. 대칭키를 쓰는 블럭 암호이다. 높은 안전성과 속도로 인해 인기를 얻어 전 세계적으로 많이 사용되고 있다.

특징

대칭형, 블럭 암호화 알고리즘이다.
대칭형 암호화 알고리즘 중 가장 유명하다.
암호화 키는 128, 192, 256의 세 가지 중 하나가 될 수 있으며, 각각 AES-128, AES-192, AES-256으로 불린다.

[2026년 추가] 몇 년 지나 다시 읽어보니 아래 예제에 보안상 문제가 있어서 짚고 넘어간다. 원본 코드는 “당시 작성한 그대로” 남겨두고, 아래쪽에 고친 버전을 추가했다.

  • IV(초기화 벡터)를 고정값으로 쓰고 있음: IV = secretKey.substring(0, 16) — 매번 같은 IV를 재사용한다. IV의 목적 자체가 “같은 평문을 여러 번 암호화해도 매번 다른 암호문이 나오게” 하는 것인데, 고정 IV를 쓰면 이 목적이 완전히 무너진다. 같은 값을 두 번 암호화하면 결과도 항상 똑같이 나오고, 이는 공격자가 패턴을 분석할 실마리를 준다.
  • AES/CBC/PKCS5Padding 조합은 패딩 오라클 공격(Padding Oracle Attack)에 취약할 수 있음: CBC 모드는 인증(무결성 검증)을 해주지 않아서, 암호문이 변조돼도 복호화 시 에러 메시지 차이로 원문을 유추당할 위험이 있다. 요즘은 암호화와 동시에 인증까지 해주는 AES/GCM 모드를 기본으로 권장한다.
  • 비밀키를 소스코드에 하드코딩: 이건 AES256 자체의 문제는 아니지만, 실무에서는 키를 소스에 두지 않고 환경변수나 AWS KMS/Vault 같은 시크릿 매니저로 분리해야 한다.

AES256Cipher.java (예전 예제 - 위에서 지적한 문제가 있음)

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;

import org.apache.commons.codec.binary.Base64;

public class AES256Cipher {

    private static volatile AES256Cipher INSTANCE;

    final static String secretKey = "jmlim12345bbbbbaaaaa123456789066"; //32bit
    static String IV = ""; //16bit

    public static AES256Cipher getInstance() {
        if (INSTANCE == null) {
            synchronized (AES256Cipher.class) {
                if (INSTANCE == null)
                    INSTANCE = new AES256Cipher();
            }
        }
        return INSTANCE;
    }

    private AES256Cipher() {
        IV = secretKey.substring(0, 16);
    }

    //암호화
    public static String AES_Encode(String str) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        byte[] keyData = secretKey.getBytes();

        SecretKey secureKey = new SecretKeySpec(keyData, "AES");

        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(IV.getBytes()));

        byte[] encrypted = c.doFinal(str.getBytes("UTF-8"));
        String enStr = new String(Base64.encodeBase64(encrypted));

        return enStr;
    }

    //복호화
    public static String AES_Decode(String str) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        byte[] keyData = secretKey.getBytes();
        SecretKey secureKey = new SecretKeySpec(keyData, "AES");
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(IV.getBytes("UTF-8")));

        byte[] byteStr = Base64.decodeBase64(str.getBytes());

        return new String(c.doFinal(byteStr), "UTF-8");
    }
}

AES256CipherTest.java


import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class AES256CipherTest {
    String id = "hackerljm";
    String custrnmNo = "1234";
    String custNm = "정묵테스트";

    @Test
    public void encDesTest() throws InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        AES256Cipher a256 = AES256Cipher.getInstance();

        String enId = a256.AES_Encode(id);
        String enYyyymmdd = a256.AES_Encode(custrnmNo);
        String enCustNm = a256.AES_Encode(custNm);

        String desId = a256.AES_Decode(enId);
        String desYyyymmdd = a256.AES_Decode(enYyyymmdd);
        String desCustNm = a256.AES_Decode(enCustNm);

        assertThat(id, is(desId));
        assertThat(custrnmNo, is(desYyyymmdd));
        assertThat(custNm, is(desCustNm));
    }
}

AES256GcmCipher.java (수정된 예제 - GCM 모드 + 매 암호화마다 랜덤 IV)

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AES256GcmCipher {

    private static final int GCM_TAG_LENGTH_BIT = 128;
    private static final int GCM_IV_LENGTH_BYTE = 12; // GCM 권장 IV 길이

    private final SecretKey secretKey;

    // 비밀키는 생성자로 주입받는다 - 소스에 하드코딩하지 않고 환경변수/시크릿 매니저에서 가져온 값을 넘겨줄 것
    public AES256GcmCipher(byte[] key32Byte) {
        this.secretKey = new SecretKeySpec(key32Byte, "AES");
    }

    // 암호화 - 호출할 때마다 새 IV를 생성하고, IV를 암호문 앞에 붙여서 함께 반환
    public String encrypt(String plainText) throws Exception {
        byte[] iv = new byte[GCM_IV_LENGTH_BYTE];
        new SecureRandom().nextBytes(iv); // 매번 랜덤 IV - 이게 원본 예제의 가장 큰 문제를 고치는 부분

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, iv));

        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));

        // IV(12바이트) + 암호문을 이어붙여 하나의 값으로 저장 -> 복호화 시 앞 12바이트를 다시 IV로 꺼내 씀
        byte[] combined = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);

        return Base64.getEncoder().encodeToString(combined);
    }

    // 복호화 - 앞 12바이트를 IV로 분리한 뒤 나머지를 복호화
    public String decrypt(String cipherTextBase64) throws Exception {
        byte[] combined = Base64.getDecoder().decode(cipherTextBase64);

        byte[] iv = new byte[GCM_IV_LENGTH_BYTE];
        byte[] encrypted = new byte[combined.length - GCM_IV_LENGTH_BYTE];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, iv));

        return new String(cipher.doFinal(encrypted), "UTF-8");
    }
}
// 사용 예 - 키는 실제로는 환경변수/시크릿 매니저에서 로드
byte[] key = System.getenv("AES_SECRET_KEY").getBytes("UTF-8"); // 32byte(256bit)
AES256GcmCipher cipher = new AES256GcmCipher(key);

String encrypted = cipher.encrypt("정묵테스트");
String decrypted = cipher.decrypt(encrypted);

GCM은 IV를 암호문마다 다르게 쓰는 대신, 그 IV 자체는 비밀로 감출 필요가 없다(공개돼도 안전하도록 설계됨) — 그래서 위처럼 IV를 암호문 앞에 그냥 붙여서 저장해도 된다. 다만 같은 키로 같은 IV를 두 번 쓰는 것만큼은 절대 피해야 하며, SecureRandom으로 매번 새로 생성하는 것이 이를 보장하는 가장 간단한 방법이다.

참고:

  • https://dukeom.wordpress.com/2013/01/08/aes256-%EC%95%94%ED%98%B8%ED%99%94-java-%EC%83%98%ED%94%8C/
  • https://namu.wiki/w/AES
  • OWASP Cryptographic Storage Cheat Sheet

Spring boot 환경에서 Spring Session을 통해 WAS가 아닌 다른 저장소에 세션 저장하기.

|

WAS의 세션을 이용했을 때, 서버가 한 대일 때에는 WAS의 세션을 사용하는데에 문제가 없었을 것이다. 사용자가 증가함에 따라 서버에 과부하가 걸려 WAS를 증설하기 시작할 경우 HttpSession을 사용하면 세션이 두군데서 관리된다.

ex) 로그인 요청은 1번 서버로 들어와 1번 서버의 세션에 담았는데, 그 다음 요청을 2번에 하게 된다면?? 로그인 상태가 아니다.

그렇기 때문에 단일 서버 환경에서 세션 기능을 사용하기 위해서는 HttpSession 객체를 활용해도 상관없지만, 멀티 서버 환경에서 세션 기능을 사용하기 위해서는 세션 정보를 Redis와 같은 DB에 저장해야 한다.

리눅스에 Redis 설치하기.

먼저 리눅스에 redis를 설치하기 위해서 파일을 직접 다운받거나 apt-get 등을 이용할 수 있다. 만약 apt-get 패키지 다운로드를 사용할 경우 아래와 같이 커맨드를 입력하면 된다.

$ sudo apt-get install redis-server

redis 동작하기 –

$ redis-server

Spring Boot에서 Spring session + redis 로 세션 관리 하기.

의존성 추가.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
</dependency>

application.yml 추가.

spring: 
  redis:
    host: 192.168.0.156 # 레디스가 설치되어있는 서버의 아이피
    port: 6379

Spring Session Configuration 추가.

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig extends AbstractHttpSessionApplicationInitializer {
} 

[2026년 추가] 지금 기준으로 이 글을 그대로 따라 하면 두 군데서 막힌다.

  1. spring-session 아티팩트는 지금은 범용 코어일 뿐이고, Redis 연동에는 spring-session-data-redis(groupId: org.springframework.session)를 받아야 한다.
  2. Spring Boot 3.0부터 Redis 설정 프리픽스가 spring.redis.* 에서 spring.data.redis.* 로 바뀌었다. (공식 문서 확인) 그대로 spring.redis.host를 쓰면 무시되고 기본값(localhost:6379)으로 접속을 시도하게 된다.
    spring:
      data:
     redis:
       host: 192.168.0.156
       port: 6379
    

    참고로 최신 Spring Boot에서는 spring-session-data-redis 의존성만 추가하면 @EnableRedisHttpSession 없이도 자동 설정(auto-configuration)되는 경우가 많다 — 커스터마이징이 필요할 때만 명시적으로 @EnableRedisHttpSession을 쓰면 된다.

레디스에서 세션 저장여부 확인.

참고:

  • http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=8552454
  • https://webisfree.com/2018-01-25/%EB%A6%AC%EB%88%85%EC%8A%A4%EC%97%90-redis-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-install

스프링부트에서 다국어 기능 사용하기.

|

기존 Spring + java web application 다국어 지원 설정을 하기 위해선 여러가지 사항들을 고려 해야 한다. spring framework 및 설정을 통해서 다국어를 사용하는 방법에 여러가지가 있다.

하지만 Spring Boot의 가장 큰 장점은 대부분의 설정이 이미 되어있어서 별로로 설정하지 않아도 사용 가능하다. MessageSource 설정도 역시 이미 되어있다.

추기로 이 블로그에선 yml 파일을 사용하여 다국어를 설정해 보는 방법을 작성하였다.

다국어 관련 Config 파일 작성.

yml YamlResourceBundle 클래스를 사용하기 위한 pom.xml 파일 디펜던시 추가.

<!-- https://mvnrepository.com/artifact/net.rakugakibox.util/yaml-resource-bundle -->
<dependency>
  <groupId>net.rakugakibox.util</groupId>
  <artifactId>yaml-resource-bundle</artifactId>
  <version>1.1</version>
</dependency>

Configuration 작성.

package 파일이 들어갈 패키지명;

import net.rakugakibox.util.YamlResourceBundle;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

// (2026년 수정) WebMvcConfigurerAdapter는 Spring 5부터 deprecated, Spring 6에서 삭제됨.
// 자바 8의 인터페이스 default method 덕분에 어댑터 클래스 없이 WebMvcConfigurer를 바로 구현하면 된다.
@Configuration
public class MessageSourceConfig implements WebMvcConfigurer {

     @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.KOREA);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        return lci;
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
    
    @Bean("messageSource")
    public MessageSource messageSource(
            @Value("${spring.messages.basename}") String basename,
            @Value("${spring.messages.encoding}") String encoding
    ) {
        YamlMessageSource ms = new YamlMessageSource();
        ms.setBasename(basename);
        ms.setDefaultEncoding(encoding);
        ms.setAlwaysUseMessageFormat(true);
        ms.setUseCodeAsDefaultMessage(true);
        ms.setFallbackToSystemLocale(true);
        return ms;
    }
}

class YamlMessageSource extends ResourceBundleMessageSource {
    @Override
    protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE);
    }
}

src/main/resources/appilcation.yml 에 다국어 설정파일이 있는 경로 추가.

spring: 
 .....
  messages:
    basename: i18n/messages
    encoding: UTF-8

src/main/resources/i18n/messages.yml 파일

alert:
 confirm: "확인"
 cancel: "취소"
 loginEmail: "아이디(이메일)를 입력해 주세요."
 loginPassword: "비밀번호를 입력해 주세요."
 loginFail: "아아디(이메일) 또는 비밀번호가 올바르지 않습니다." 

src/main/resources/i18n/messages_en.yml 파일

alert:
 confirm: "Ok"
 cancel: "Cancel"
 loginEmail: "Please enter your ID(Email)."
 loginPassword: "Please enter a password."
 loginFail: "The email or password is invalid." 

src/main/resources/i18n/messages_jp.yml 파일

alert:
 confirm: "確認"
 cancel: "取り消し"
 loginEmail: "ID(Eメール)を入力してください"
 loginPassword: "パスワードを入力してください"
 loginFail: "ID(Eメール)またはパスワードをもう一度ご確認ください。" 

src/main/resources/i18n/messages_zh.yml 파일.

alert:
 confirm: "确认"
 cancel: "取消"
 loginEmail: "请输入用户名(邮箱)。"
 loginPassword: "请输入密码。"
 loginFail: "请重新确认用户名(邮箱)或者密码。" 

jsp 페이지에서 사용하기

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <-- 이와같이 태그 선언 후 

<spring:message code="alert.loginEmail" /> 원하는 텍스트가 쓰여진 메세지 코드값 정의

thymeleaf 페이지에서 사용하기.


  ...
  <div>
    <span th:text="#{alert.loginEmail}">아이디(이메일)를 입력해 주세요.</span>
  </div>
  ...

default url 조회

영문조회 시 (lang=en)

일문 조회시 (lang=jp)