클라이언트가 서버에 접속하면 서버는 클라이언트에게 인증서를 전달하면 클라이언트는 이 인증서를 보고 신뢰할 수 있는 사람인지 확인 후 데이터를 보내는 등 다음 절차를 진행한다.
여기서 보통 SSL을 잘 몰랐다. 크롬이나 브라우저창에 www 또는 https를 볼 수 있다. 여기서 HTTPS는 HTTP와 TLS가 조합된 프로토콜만을 말한다. 여기서 TLS는 SSL의 후속 버전이지만 아직 SSL이 더 많이 사용되는 용어같다. 그렇지만 일단 둘 다 기억은 해두자!!
SSL을 쓰는 이유를 정리해보면 전달되는 내용이 다른 사람에게 노출되는 것을 막을 수 있으며 접속하는 클라이언트가 신뢰할 수 있는 서버인지 알 수 있고 전달되는 내용이 변경되는 것을 막을 수 있다.
통신 단계는 3단계다.
1. 핸드셰이크 2. 전송 3. 종료
여기서 가장 중요한건 핸드셰이크다. 헨드셰이크는 공개키와 대칭키 2가지 방법을 함께 사용한다.
1. Client Hello : 서버에 접근하여 클라이언트는 현재 자신이 가능한 암호화 방식과 랜덤 데이터를 서버에게 전송한다.
2. Server Hello : 서버는 클라이언트에게 제시한 암호화 방식 중 하나를 선정하여 알려주고 서버의 인증서를 전달한다. 또 서버에서도 랜덤 데이터를 생성하여 전달한다.
3. Client Pre Master Secret 생성 : 클라이언트는 자신이 가지고 있는 CA 리스트에서 서버가 보낸 인증서를 복호화 한다. 만약 성공 했을 시 클라이언트와 서버가 전송한 랜덤한 데이터를 조합하여 Pre Master Secret 키를 생성한다. 그 후 인증서에 있는 공개키를 이용하여 Pre Master Secret 키를 암호화 하여 서버로 전송한다. (이 때 Pre Master Secret는 절대 노출되면 안된다.)
4. Server Pre Master Secret 암호화 : 서버는 Pre Master Secret 키를 자신의 비밀키로 복호화하여 클라이언트와 서버는 서로 Pre Master Secret 키를 공유한 상태가 된다.
5. Session Key 생성 추후 Pre Master Secret 키를 이용하여 Master Secret 키를 만들고 Session Key를 만들어 세션을 형성하고 대칭키 방식으로 데이터를 주고 받는다.
회사에서 Putty로 서버에 많이 접속한다. 근데 그때마다 vi로 편집할 때가 많은데 vi 단축키를 잘 모르니 많이 불편했다. 그런데 유튜브를 보다가 알고리즘을 통해 영상을 하나 보게 됐는데 VSCode에서 아주 유용한 툴을 발견했다. VSCode에서 SSH 접속을 하여 디렉토리 목록도 편집기에서 노출시켜서 너무 편하게 작업을 할 수 있는 툴이다. 한번 보자.
1. 확장탭에서 Remote Development를 검색하여 설치한다.
2. F1를 눌러서 SSH를 검색하면 많은 메뉴 중에 Open Configuration File...있을 것인데 그것을 누르면 내 PC의 SSH config 파일을 선택한다. (C:\Users\사용자명\.ssh\config)
3. config 파일을 열면 처음에는 아래처럼 있다.
파일 안에 호스트랑 유저를 입력하고 저장한다.
Host [계정명]@[IP 주소]:[포트번호]
HostName [IP 주소]:[포트번호]
User [계정명]
4. 이제 연결을 해보면된다. F1 누른 후 Remote-SSH:Connect to Host...를 선택한다.
그러면 아까 위에서 config 파일에 저장해둔 연결 호스트가 나온다.
나는 이 부분에서 잘 안됐다. 그래서 나는 config 파일에 저장한게 안되니 ADD New SSH Host...를 하여 새로 입력하고 config 파일에 저장해줬다.
ssh 계정명@ip주소:포트번호(또는 도메인) 형식으로 입력하고하고 Enter 키를 누르면 어디 파일에 저장해야될지 선택하면 된다.
근데 이렇게 하면 계정이름이 윈도우 계정이름으로 잡힌다. config 파일을 열어서 Host 부분을 보면 IP주소와 포트번호만 써있으니 Host IP주소 앞에 User명@IP주소:포트번호로 바꿔주자
결과적으로 config에 먼저 들어가서 저장할 필요는 없다.
5. Host를 선택하면 해당 SSH 의 운영체제를 선택 후 패스워드를 입력하면 끝난다.
VSCode에서 원격 디렉토리 접근이 가능하며 편집도 가능하고 터미널까지 가능하다. 너무 편리하다. 지금까진 파일질라로 PC에서 파일 편집해서 파일질라로 옮기고 했는데 그럴 필요가 없어졌다.
왠만한거 다 세팅했는데 공개 테스트에 App Bundle을 올리고 버전 검토를 누르고 기다리는데 아래 처럼 오류가 발생했다.
앱에 버전 코드 1의 APK가 있으므로 android.permission.CAMERA, android.permission.RECORD_AUDIO, android.permission.READ_PHONE_STATE, android.permission.READ_CONTACTS 권한이 필요합니다. APK에서 이 권한을 사용하는 앱에는 개인정보처리방침을 설정해야 합니다.
대충 먼소리냐 특정 권한을 사용하려면 개인정보처리방침 URL을 등록해야된다.
콘솔이 예전이랑 많이 달라져서 당황했다. 처음 세팅할 때 나왔어야 했는데 앱을 업로드하고 검토 누르니깐 대시보드에 생겼다. 개인정보처리방침을 URL에 등록하자.
파일을 다루기 때문에 업로드할 파일이나 다운로드할 파일의 관련 설정을 먼저 해야한다(업로드 경로, 업로드 크기 설정, 다운로드 파일 크기 등...) 프로젝트 안에 application.properties나 application.yml이 있을 것이다. 관련 설정 포함 시켜주자.
보통 파일이름을 오리지널 그대로 올리는 경우가 있는데 그렇게 해선 안된다. 이름이 겹치면 올라가지 않을뿐더라 나중에 보안에 문제가 생길 수 있다. 원래 파일이름을 통신을 통해 보내서 DB에 저장하고 파일이름 설계에 의한 이름 + 타임스태프로 해주는 경우가 많으니 참고바란다. 우선 난 타임스탬프 + _파일이름으로 해놓을 예정이다. 물론 Response에서 오리지널 이름과 변환 이름 둘 다 반환한다.
package com.example.filedemo.service;
import com.example.filedemo.property.config.Properties;
import com.example.filedemo.exception.FileStorageException;
import com.example.filedemo.exception.MyFileNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@Service
public class FileStorageService {
private final Path fileStorageLocation;
@Autowired
public FileStorageService(Properties Properties) {
this.fileStorageLocation = Paths.get(Properties.getUploadPath())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file) {
SimpleDateFormat sdf = new SimpleDateFormat ("yyyyMMddhhmmss_");
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
String timeStamp = sdf.format(timestamp);
String fileName = timeStamp + StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
public Resource loadFileAsResource(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if(resource.exists()) {
return resource;
} else {
throw new MyFileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
throw new MyFileNotFoundException("File not found " + fileName, ex);
}
}
}
5. 예외 클래스
파일을 업로드나 다운로드할 때 예외가 발생을 경우를 처리해 줄 예외클래스를 만들어준다.
package com.example.filedemo.exception;
public class FileStorageException extends RuntimeException {
public FileStorageException(String message) {
super(message);
}
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
package com.example.filedemo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class MyFileNotFoundException extends RuntimeException {
public MyFileNotFoundException(String message) {
super(message);
}
public MyFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
6. 테스트
백엔드 개발이 완료 됐으니 API를 Postman으로 테스트를 해보자.
지금 같은 경우 Controller에서 RequestMapping을 해주었다. 그렇기 때문에 주소가 /localhost:8080/api/file/uploadfile이 된다.