SSL 인증서란 클라이언트와 서버간의 통신을 제 3자가 보증을 해주는 문서이다.

 

클라이언트가 서버에 접속하면 서버는 클라이언트에게 인증서를 전달하면 클라이언트는 이 인증서를 보고 신뢰할 수 있는 사람인지 확인 후 데이터를 보내는 등 다음 절차를 진행한다.

 

여기서 보통 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를 만들어 세션을 형성하고 대칭키 방식으로 데이터를 주고 받는다.

 

 

 

 

 

'IT > general' 카테고리의 다른 글

[UUID] Universally Unique Identifiers - 범용 고유 식별자  (0) 2022.02.09
로깅(Logging)?  (0) 2021.12.14
[방화벽] 기본 포트 차단  (0) 2021.11.22
[VSCode] 원격 SSH 접속  (0) 2021.10.08
[IDE] 이클립스 설치  (0) 2021.06.03

회사에서 개발 도중 해당 페이지가 안열린다고 연락을 받았다. 물론 내가 엔지니어는 아니기 때문에 잘 모르지만...

 

트래픽 모니터를 보니 계속 차단된다고 나온다...

 

해당 방화벽은 기본 차단 포트가 있다고 한다. 각 포트마다 이유가 있었다.

혹여나 포트를 개방했는데 접속이 안된다면 이런게 있을 수 있으니 찾아보자

'IT > general' 카테고리의 다른 글

로깅(Logging)?  (0) 2021.12.14
SSL(Secure Sockets Layer)  (0) 2021.12.07
[VSCode] 원격 SSH 접속  (0) 2021.10.08
[IDE] 이클립스 설치  (0) 2021.06.03
[가상화폐] 가정용 데스크탑 PC 이더리움 채굴  (0) 2021.05.28

이전에 forever을 작성을 했었다.

 

얼마 뒤 다른 분과 협업을 하다 보니 그쪽은 pm2를 사용하여서 작성하게 됐다.

 

너무 바쁘기 때문에 쓰레드라던지 등등을 자세하게 파헤치진 않았다. 

 

대충 아는건 멀티 코어 혹은 하이퍼 스레딩을 사용가능하다 정도... forever도 이게 가능한지는 모르겠다.

 

아무튼 내가 체감되는 것들만 맨 끝에 남긴다. 

 

우선 pm2를 설치한다.(forever랑 명령어 방식이 거의 유사하다.)

 

$ > npm install pm2 -g

 

pm2 명령어를 사용하여 실행시켜본다. 

pm2 start app.js

프로세스 중지

pm2 stop 0 (프로세스 번호)

프로세스 삭제

pm2 del 0 (프로세스 번호)

프로세스 전체 삭제

pm2 kill

프로세스 전체 로그 - 이 부분이 forever 과 달랐다. 특정 프로세스의 로그를 출력하는 방법을 모르겠다. 현재 pm2로 실행되고 있는 프로세스 전체의 로그가 한번의 출력 된다.

pm2 log

프로세스 목록

pm2 ls

실행 중인 프로세스 모니터

pm2 monit

 

회사에서 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에서 파일 편집해서 파일질라로 옮기고 했는데 그럴 필요가 없어졌다.

 

'IT > general' 카테고리의 다른 글

SSL(Secure Sockets Layer)  (0) 2021.12.07
[방화벽] 기본 포트 차단  (0) 2021.11.22
[IDE] 이클립스 설치  (0) 2021.06.03
[가상화폐] 가정용 데스크탑 PC 이더리움 채굴  (0) 2021.05.28
[통신 프로토콜] MQTT  (0) 2021.05.21

서버를 계속 운용하다보니 포트가 제대로 열렸는지 확인해야될 경우가 생겼다. 그래서 간단하게 알아보자

 

netstat -nap

위 명령어는 열려 있는 모든 포트를 확인한다.

 

netstat -nap | grep LISTEN

위 명령어는 현재 LISTEN 중인 포트만 확인할 수 있다.

 

netstat -nap | grep 3306

위 명령어는 특정 포트를 확인할 수 있다.

 

위 명령어를 통해서 포트에 해당하는 프로세스 아이디를 가지고 프로세스를 죽일 수 있다.

kill -9 3306

외주를 준 프로젝트 백엔드 소스코드를 받았는데 로컬테스트를 하기 위해 내가 사용하는 PC에서 구동을 해보려 하지만 모듈이 설치가 안된다. node와 npm 버전 때문인 것 같다. 그래서 업데이트를 해보려고한다.

 

 

Node.JS 공홈에 나와 있는 방법을 우선 올린다.

 

윈도우는 그냥 패키지 매니저 홈페이지에서 다운 받아서 원래 있던 폴더에 덮어쓰면 된다.

 

 

아마 검색을 하면 n 으로 Node.JS 패키지를 설치를 하는게 많을텐데 괜히 힘 빼지 말자 n은 리눅스 계열이다.

나도 윈도우에서 주구장창 했다... 결과 안된다. 아래처럼 EBADPLATFORM 에러가 발생한다. 대충 써보면 내가 사용중인 OS는 지원을 하지않는다는 얘기다. 

각 OS 마다 업데이트 방법이 다르니 공홈을 참고하자.

왠만한거 다 세팅했는데 공개 테스트에 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이 있을 것이다. 관련 설정 포함 시켜주자.

 

1. 설정

spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=2KB
spring.servlet.multipart.max-file-size=200MB
spring.servlet.multipart.max-request-size=215MB
upload-path=/User/upload

이때 주의할 점은 업로드 경로다. 절대주소에서 최상위 디렉토리(Root)만 빠진 주소다. 최상위 디렉토리를 프로젝트 폴더가 C에 있나 D에 있나로 결정된다. 그래서 프로젝트 폴더가 C/D 드라이브 등 잘 파악하고 하자. 

 

2. Controller - 파일 업로드 , 파일 멀티 업로드, 파일 다운로드 3개로 구성된다.

가끔 다운로드가 안되는 경우가 있다. 코드에 문제는 아니다. 다만 설정을 잘못했을 것이다...

RequestMapping 주소가 있기 때문에 파일 업로드시 다운로드 Uri에 RequestMapping이 추가 됐는지 확인하자.

package com.example.filedemo.controller;

import com.example.filedemo.domain.FileUpload;
import com.example.filedemo.service.FileStorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/file")

public class FileController {

    private static final Logger logger = LoggerFactory.getLogger(FileController.class);

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private FileService fileService;
    
    @PostMapping("/uploadfile")
    public File uploadFile(@RequestParam("file") MultipartFile file) {
        if(file.isEmpty()){
            //파일 업로드가 안됐을 시 처리
        }
        String fileName = fileStorageService.storeFile(file);
        //확장자만 추출하는 형태 ex) exe , png, jpg ...
        String fileExt = fileName.replaceAll("^.*\\.(.*)$", "$1");
        
        String fileOriginalName = StringUtils.cleanPath(file.getOriginalFilename());

        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                .path("/file/downloadfile/")
                .path(fileName)
                .toUriString();

        return new File(fileName, fileOriginalName, fileExt, fileDownloadUri);
    }
    
	@PostMapping("/uploadmultiplefiles")
    public List<File> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
        return Arrays.asList(files)
                .stream()
                .map(file -> uploadFile(file))
                .collect(Collectors.toList());
    }
    
	@GetMapping("/downloadfile/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
        // Load file as Resource
        Resource resource = fileStorageService.loadFileAsResource(fileName);

        String contentType = null;
        try {
            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException ex) {
            logger.info("Could not determine file type.");
        }

        // Fallback to the default content type if type could not be determined
        if(contentType == null) {
            contentType = "application/octet-stream";
        }

        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
}

 

3. 파일 업로드에 대한 Response 해줄 클래스다. DTO 쯤으로 생각한다. 별 내용없고 파일 이름, 다운로드 경로, 사이즈 등등...을 저장하는 클래스다. 아마 경우에 따라 필요하지 않아서 생성하지 않을수도 있지만 이번에는 생성하는 방식으로 개발했다.

package com.example.filedemo.domain;

@Getter
@Setter
@AllArgsConstructor
@ToString
public class FileUpload {
    private String fileName;
    private String fileOriginalName;
    private String fileDownloadUri;
    private String fileExt;
    private long size;
    
    public FileUpload(String fileName, String fileOriginalName, String fileExt, String fileDownloadUri) {
        this.fileName = fileName;
        this.fileOriginalName = fileOriginalName;
        this.fileExt = fileExt;
        this.fileDownloadUri = fileDownloadUri;
    }
}

4. 서비스

 

이제 직접적으로 파일 업로드/다운로드의 서비스 로직을 만들 차례다. 

만약 맨 처음 파일 경로가 안 읽어질수도 있다. 주의하자.

파일 업로드 시 파일 이름 등을 바꾸고 싶다면 storeFile()을 보면 된다.

보통 파일이름을 오리지널 그대로 올리는 경우가 있는데 그렇게 해선 안된다. 이름이 겹치면 올라가지 않을뿐더라 나중에 보안에 문제가 생길 수 있다. 원래 파일이름을 통신을 통해 보내서 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이 된다.

'IT > Spring' 카테고리의 다른 글

[Spring Boot] Spring Boot 입문 - 프로젝트 세팅  (0) 2021.06.22
[Spring] Spring Framework 이클립스 세팅  (0) 2021.06.03

+ Recent posts