블로그

RestTemplate에 Basic Authentication 적용하기

웹 애플리케이션에 Basic Authentication을 적용했을 때 REST 호출시 Basic Authentication이 적용되도록 해야 합니다. Spring의 RestTemplate 에는 Interceptor를 추가할 수 있도록 되어 있어서 다음과 같이 Basic Authentication을 자동으로 할 수 있도록 지원합니다.

RestTemplate restTemplate = new RestTemplate();

restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor("username", "password"));

(눈금) Apache HttpClient에서 Basic Authentication을 사용하려면 https://www.baeldung.com/httpclient-4-basic-authentication을 참고하십시오.

Self Signed Certification을 수용하는 RestTemplate

자기 스스로가 CA가 되어 인증서를 자기 서명을 하여 웹 서버에 배포하는 경우 Self Signed Certification을 허용할 수 있도록 RestTemplate 을 구성할 필요가 있습니다.

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

public class Tester {

    public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        TrustSelfSignedStrategy strategy = new TrustSelfSignedStrategy(); // 중요!!

        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                .loadTrustMaterial(null, strategy)
                .build();

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(csf)
                .build();

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

        requestFactory.setHttpClient(httpClient);

        RestTemplate restTemplate = new RestTemplate(requestFactory);

        ResponseEntity<String> forEntity = restTemplate.getForEntity(URL, String.class);
    }
}


RestTemplate 으로 HTTPS가 적용되어 있는 사이트를 호출하는 경우 인증서에 대한 유효성 검사가 이루어져야 하지만 이를 무시하고 HTTPS로 호출하고자 하는 경우 다음과 같이 코드를 작성할 수 있습니다.

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

public class Tester {

    public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                .loadTrustMaterial(null, acceptingTrustStrategy)
                .build();

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(csf)
                .build();

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

        requestFactory.setHttpClient(httpClient);

        RestTemplate restTemplate = new RestTemplate(requestFactory);

        ResponseEntity<String> forEntity = restTemplate.getForEntity("https://www.amazon.com/", String.class);
        System.out.println(forEntity.getBody());
    }
}

이 코드가 동작하려면 Maven POM에 다음을 추가해야 합니다.

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>


@JsonUnwrapped를 이용한 펼치기

Spring JPA 또는 Domain Object를  REST로 서비스를 할 때 클라이언트쪽으로 전달시 JSON으로 변환을 하게 됩니다. JPA의 경우 복합키를 표현하는 경우 다음과 같이 @Embeddable annotation을 써서 표현할 수 있습니다. 

@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Embeddable
@NoArgsConstructor
public class PayId implements Serializable {

    /**
     * 결제번호 (비즈니스적으로 유의미한 번호)
     */
    @EqualsAndHashCode.Include
    @Column
    private Long payNumber;

    @EqualsAndHashCode.Include
    @Column
    private Long paySeq;

    public PayId(Long payNumber, Long paySeq) {
        this.payNumber = payNumber;
        this.paySeq = paySeq;
    }
}

그러나 이렇게 사용하는 경우 다음과 같이 Entity에 PK로 정의를 해야 하며 이 경우 JSON으로 변환하면 계층 구조를 갖도록 생성이 됩니다.

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@NoArgsConstructor
public class PayDetailId implements Serializable {

    @EqualsAndHashCode.Include
    private PayId payId;

    @EqualsAndHashCode.Include
    @Column
    private Long payDetailId;

    public PayDetailId(PayId payId, Long payDetailId) {
        this.payId = payId;
        this.payDetailId = payDetailId;
    }
}

예를 들면 이렇게 변환이 됩니다. 만약 payNumber를 payId 아래에 두지 않고 payId 와 같은 레벨에 두고자 한다면 @JsonUnwrapped annotation이 제격입니다.

{
    "payId" : 
    {
        "payNumber": 100,
        "paySeq" : 5000
    },
    "payDetailId": 1
}

다음과 같이 펼쳐서 전송하고자 하는 경우 다음과 같이 @JsonUnwrapped annotation을 추가합니다.

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@NoArgsConstructor
public class PayDetailId implements Serializable {

    @EqualsAndHashCode.Include
    @JsonUnwrapped
    private PayId payId;

    @EqualsAndHashCode.Include
    @Column
    private Long payDetailId;

    public PayDetailId(PayId payId, Long payDetailId) {
        this.payId = payId;
        this.payDetailId = payDetailId;
    }
}

그러면 아래와 같이 자동 생성됩니다.

{
    "payNumber": 100,
    "paySeq": 5000,
    "payDetailId": 1
}

다음은 Spring Data REST를 사용하기 위해서 Domain을 하나 생성한 예제입니다.

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.web.bind.annotation.CrossOrigin;

@CrossOrigin
@RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends CrudRepository<WebsiteUser, Long> {

    @Override
    @RestResource(exported = false)
    void delete(WebsiteUser entity);

    @Override
    @RestResource(exported = false)
    void deleteAll();

    @Override
    @RestResource(exported = false)
    void deleteAll(Iterable<? extends WebsiteUser> entities);

    @Override
    @RestResource(exported = false)
    void deleteById(Long aLong);

    @RestResource(path = "byEmail", rel = "customFindMethod")
    WebsiteUser findByEmail(@Param("email") String email);
}

Spring Data REST에서는 Domain Class에 대해서 CRUD를 수행하는 CrudRepository 를 구현했을 때 자동으로 REST API를 생성합니다. 이때 특정한 HTTP Method만 금지시키고자 하는 경우 다음과 같이 코드를 작성할 수 있습니다.

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ExposureConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;

@Configuration
public class RestConfig implements RepositoryRestConfigurer {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration repositoryRestConfiguration) {
        ExposureConfiguration config = repositoryRestConfiguration.getExposureConfiguration();
        config.forDomainType(WebsiteUser.class).withItemExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.PATCH));
    }
}


Swagger의 spring-plugin-core-1.2.0.RELEASE.jar 오류

이 이슈는 보통 Spring Data REST에 Swagger를 같이 사용하고자 하는 경우 발생합니다.

Spring Data REST에 Swagger를 추가하면 spring-plugin-core-1.2.0.RELEASE.jar  파일에 대한 충돌 문제가 발생합니다. 이 문제는 Swagger의 버전이 낮아서 생기는 문제이긴 하나 현재 Swagger 3.0 Snapshot 버전을 통해서 해결할 수 있습니다.

우선 이 문제를 해결하기 위해서 Maven POM에 다음의 Repository를 추가합니다.

<repositories>
    <repository>
        <id>jcenter-snapshots</id>
        <name>jcenter</name>
        <url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
    </repository>
</repositories>

그리고 난 후 다음과 같이 Swagger Snapshot 버전을 Maven POM에 추가합니다.

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-data-rest</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>

이제 Spring Boot Configuration에 다음을 추가합니다. 참고로 버전업이 되면서 Annotation 명이 @EnableSwagger2WebMvc 으로 변경되었습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.data.rest.configuration.SpringDataRestConfiguration;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Configuration
@EnableSwagger2WebMvc
@Import(SpringDataRestConfiguration.class)
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any()).build();
    }
}

이제 Spring Boot Application을 실행하고 http://localhost:8080/swagger-ui.html로 접속해봅니다.

Spring Data REST의 HAL Explorer 사용하기

HAL Browser, HAL Explorer에 대해서

Spring Data REST에서는 기존에 HAL Browser를 제공하였으나 deprecated 상태이며 대체로 HAL Explorer를 사용하도록 권고하고 있습니다.

Spring Data REST를 사용할 때 개발을 편리하게 하기 위해서 HAL Explorer를 사용할 수 있습니다. HAL Explorer를 Maven POM에 다음을 추가합니다.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-rest-hal-explorer</artifactId>
    <version>3.3.0.RELEASE</version>
</dependency>

Spring Boot Application을 실행하고 /로 접근하면 다음의 화면을 볼 수 있습니다.