Skip to content

Commit

Permalink
feat: S3 Proxy 서버에 서브모듈을 활용한 인가 과정 추가 (#684)
Browse files Browse the repository at this point in the history
* chore: s3 proxy에 config 서브모듈 추가

* feat: S3Proxy 서버에 인가 로직 추가

* refactor: S3ProxyUploader 미사용 임포트문 삭제

* fix: kafka appender 위치 변경

* chore: s3 proxy 서브모듈 최신화

* fix: AuthInterceptor 변경된 config에 맞도록 설정 변경

* refactor: Interceptor 설정될 경로 추가
  • Loading branch information
tributetothemoon authored Oct 25, 2021
1 parent 3890bc9 commit aa05210
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 20 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
path = backend/src/main/resources/infra-appender
url = [email protected]:zzimkkong/infra-appender.git
branch = main
[submodule "s3proxy/src/main/resources/config"]
path = s3proxy/src/main/resources/config
url = [email protected]:zzimkkong/config.git
branch = main
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.woowacourse.zzimkkong.config;

import com.woowacourse.zzimkkong.infrastructure.thumbnail.S3ProxyUploader;
import com.woowacourse.zzimkkong.infrastructure.thumbnail.StorageUploader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:config/s3proxy.properties")
public class S3ProxyConfig {
@Bean(name = "storageUploader")
@Profile("prod")
public StorageUploader storageUploaderProd(
@Value("${s3proxy.server-uri.prod}") final String serverUri,
@Value("${s3proxy.secret-key.prod}") final String secretKey) {
return new S3ProxyUploader(serverUri, secretKey);
}

@Bean(name = "storageUploader")
@Profile({"dev", "local", "test"})
public StorageUploader storageUploaderDev(
@Value("${s3proxy.server-uri.dev}") final String serverUri,
@Value("${s3proxy.secret-key.dev}") final String secretKey) {
return new S3ProxyUploader(serverUri, secretKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,35 @@
import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime;
import com.woowacourse.zzimkkong.exception.infrastructure.S3ProxyRespondedFailException;
import com.woowacourse.zzimkkong.exception.infrastructure.S3UploadException;
import com.woowacourse.zzimkkong.infrastructure.thumbnail.StorageUploader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Objects;

@Component
@LogMethodExecutionTime(group = "infrastructure")
public class S3ProxyUploader implements StorageUploader {
private static final String PATH_DELIMITER = "/";
private static final String API_PATH = "/api/storage";
private static final String CONTENT_DISPOSITION_HEADER_VALUE_FORMAT = "form-data; name=file; filename=%s";

private final WebClient proxyServerClient;
private final String secretKey;

public S3ProxyUploader(
@Value("${s3proxy.server-uri}") final String serverUri) {
final String serverUri,
final String secretKey) {
this.proxyServerClient = WebClient.builder()
.baseUrl(serverUri)
.build();
this.secretKey = secretKey;
}

@Override
Expand All @@ -54,6 +49,7 @@ private String requestMultipartUpload(String directoryName, MultipartBodyBuilder
.method(HttpMethod.POST)
.uri(String.join(PATH_DELIMITER, API_PATH, directoryName))
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.header(HttpHeaders.AUTHORIZATION, secretKey)
.body(BodyInserters.fromMultipartData(multipartBodyBuilder.build()))
.exchangeToMono(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.CREATED)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ThumbnailManagerImpl implements ThumbnailManager {
public ThumbnailManagerImpl(
final SvgConverter svgConverter,
final StorageUploader storageUploader,
@Value("${s3proxy.thumbnails-directory}") final String thumbnailsDirectoryName) {
@Value("${storage.thumbnails-directory}") final String thumbnailsDirectoryName) {
this.svgConverter = svgConverter;
this.storageUploader = storageUploader;
this.thumbnailsDirectoryName = thumbnailsDirectoryName;
Expand Down
3 changes: 1 addition & 2 deletions backend/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ jwt.token.secret-key=zzimkkong_secret_key_in_dev
jwt.token.expire-length=86400000

# s3
s3proxy.server-uri=http://52.78.88.220:8080
s3proxy.thumbnails-directory=thumbnails
storage.thumbnails-directory=thumbnails

# cors (delimiter == ',')
cors.allow-origin.urls=*
Expand Down
3 changes: 1 addition & 2 deletions backend/src/main/resources/application-local.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ jwt.token.secret-key=zzimkkong_secret_key_in_dev
jwt.token.expire-length=86400000

# s3
s3proxy.server-uri=http://52.78.88.220:8080
s3proxy.thumbnails-directory=thumbnails-local
storage.thumbnails-directory=thumbnails-local

# cors (delimiter == ',')
cors.allow-origin.urls=*
Expand Down
3 changes: 1 addition & 2 deletions backend/src/main/resources/application-test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ jwt.token.secret-key=zzimkkong_secret_key_in_dev
jwt.token.expire-length=86400000

# s3
s3proxy.server-uri=http://52.78.88.220:8080
s3proxy.thumbnails-directory=thumbnails-test
storage.thumbnails-directory=thumbnails-test

# cors (delimiter == ',')
cors.allow-origin.urls=*
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/resources/config
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ void invalidUrl() {

String hostName = mockGithubServer.getHostName();
int port = mockGithubServer.getPort();
S3ProxyUploader s3ProxyUploader = new S3ProxyUploader("http://" + hostName + ":" + port);
S3ProxyUploader s3ProxyUploader = new S3ProxyUploader("http://" + hostName + ":" + port, "secretKey");

String filePath = getClass().getClassLoader().getResource("luther.png").getFile();
testFile = new File(filePath);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.woowacourse.s3proxy.config;

import com.woowacourse.s3proxy.infrastructure.AuthInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:config/s3proxy.properties")
public class AuthInterceptorConfig {
@Bean(name = "authInterceptor")
@Profile("prod")
public AuthInterceptor authInterceptorProd(
@Value("${s3proxy.secret-key.prod}") String secretKey) {
return new AuthInterceptor(secretKey);
}

@Bean(name = "authInterceptor")
@Profile({"dev", "local", "test"})
public AuthInterceptor authInterceptorDev(
@Value("${s3proxy.secret-key.dev}") String secretKey) {
return new AuthInterceptor(secretKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.woowacourse.s3proxy.config;

import com.woowacourse.s3proxy.infrastructure.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AuthenticationPrincipalConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;

public AuthenticationPrincipalConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/storage/*");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.woowacourse.s3proxy.exception;

import org.springframework.http.HttpStatus;

public class AuthorizationHeaderUninvolvedException extends S3ProxyException {
private static final String MESSAGE = "인가에 실패했습니다.";
public AuthorizationHeaderUninvolvedException() {
super(MESSAGE, HttpStatus.UNAUTHORIZED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.woowacourse.s3proxy.infrastructure;

import com.woowacourse.s3proxy.exception.AuthorizationHeaderUninvolvedException;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class AuthInterceptor implements HandlerInterceptor {
private final String secretKey;

public AuthInterceptor(String secretKey) {
this.secretKey = secretKey;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (isPreflight(request)) {
return true;
}

String secretKey = AuthorizationExtractor.extractAccessToken(request);
if (secretKey.equals(this.secretKey)) {
return true;
}

throw new AuthorizationHeaderUninvolvedException();
}

private boolean isPreflight(HttpServletRequest request) {
return request.getMethod().equals(HttpMethod.OPTIONS.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.woowacourse.s3proxy.infrastructure;

import com.woowacourse.s3proxy.exception.AuthorizationHeaderUninvolvedException;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

public class AuthorizationExtractor {
private static final String AUTHORIZATION_HEADER_KEY = "Authorization";

private AuthorizationExtractor() {
}
public static String extractAccessToken(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders(AUTHORIZATION_HEADER_KEY);
if (headers.hasMoreElements()) {
return headers.nextElement();
}
throw new AuthorizationHeaderUninvolvedException();
}
}
1 change: 1 addition & 0 deletions s3proxy/src/main/resources/config
Submodule config added at 6c04ab
4 changes: 2 additions & 2 deletions s3proxy/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<springProfile name="dev">
<include resource="appenders/console-appender.xml"/>
<include resource="infra-appender/kafka-appender-dev.xml"/>
<include resource="appender/kafka-appender-dev.xml"/>

<include resource="appenders/file-appender-error.xml"/>
<include resource="appenders/file-appender-warn.xml"/>
Expand All @@ -42,7 +42,7 @@

<springProfile name="prod">
<include resource="appenders/console-appender.xml"/>
<include resource="infra-appender/kafka-appender-prod.xml"/>
<include resource="appender/kafka-appender-prod.xml"/>

<include resource="appenders/file-appender-error.xml"/>
<include resource="appenders/file-appender-warn.xml"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -25,10 +28,14 @@
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;

@PropertySource("classpath:config/s3proxy.properties")
class S3ProxyControllerTest extends AcceptanceTest {
@MockBean
S3Uploader s3Uploader;

@Value("${s3proxy.secret-key.prod}")
private String secretKey;

@BeforeEach
void setUp() {
given(s3Uploader.upload(any(MultipartFile.class), anyString()))
Expand Down Expand Up @@ -72,6 +79,7 @@ private ExtractableResponse<Response> uploadFile(String directory, File file) {
pathParameters(parameterWithName("directory").description("저장하고자 하는 스토리지 내의 디렉토리 이름"))))
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.multiPart("file", file)
.header(HttpHeaders.AUTHORIZATION, secretKey)
.pathParam("directory", directory)
.when().post("/api/storage/{directory}")
.then().log().all().extract();
Expand All @@ -85,6 +93,7 @@ private ExtractableResponse<Response> deleteFile(String directory, String fileNa
parameterWithName("directory").description("저장하고자 하는 스토리지 내의 디렉토리 이름"),
parameterWithName("filename").description("삭제하고자 하는 파일의 이름(확장자 포함)"))))
.when()
.header(HttpHeaders.AUTHORIZATION, secretKey)
.pathParam("directory", directory)
.pathParam("filename", fileName)
.delete("/api/storage/{directory}/{filename}")
Expand Down

0 comments on commit aa05210

Please sign in to comment.