콘텐츠로 이동

2021 08 24

2021-08-24

프로를 위한 웹 기술 입문 [7장. 보안을 확보하기 위한 방법]

  • 웹 어플리케이션이 지켜야 할 보안 1. 기밀성: 제3자에 대한 정보 유출 방지 2. 완전성: 제3자에 의한 악의적인 정보 수정 방지 3. 가용성: 적절한 권한을 가진 사람이 적절한 정보를 이용할 수 있게 할 것
  • 웹 애플리케이션에 대한 대표적인 공격 수법과 그 대책
    1. SQL 인젝션 - 이게 뭔데? - 패스워드 입력칸에 "aaa' or '1=1'" 등을 넣어서 SQL 결과로 레코드 가져오기 - 제 3자의 부정 로그인이나 열람 권한이 없는 정보에 대한 접근, DB의 부정 수정/삭제 같은 공격 허용, 개인정보 유출의 피해 - 해결 방법 - 입력값 Validation - PreparedStatement 활용하여 변수로 넣기 (쿼리 구조 변경이 불가) 2. 크로스 사이트 스크립팅 (XSS) - 이게 뭔데? - HTML 안에 악의적인 작동을 하는 자바스크립트를 심는 공격 수법 - 자바 스크립트 인젝션 - 해결 방법 - 새너타이징 - HTML에 심어 원래 구조 바꾸거나 파괴하는 문자열을 무해한 문자열로 변경하는 것 3. 세션 하이재킹 - 이게 뭔데? - 세션 ID를 제3자가 훔침 - 해결 방법 - XSS 대책 적용 - SSL 적용 - 세션 타임아웃 짧게 - 세션ID를 어렵게 만들기 4. 크로스 사이트 요청 위조(CSRF) - 이게 뭔데? - 공격자가 날조한 폼에서 강제적으로 정보 제출함으로써 게시판에 의도하지 않은 글 쓰거나 강제로 물건 사게 하는 공격 - 게시판에 글 쓰는 Form 요청 JS 코드를 HTML 코드안에 넣어둠 - HTML onLoad 속성 이용해 보내버리면 자기도 모르게 글 작성 - 해결 방법 - 무작위 숫자/문자열 활용한 일회용 토큰 발급 - 브라우저 form에 hidden 속성 만들어 이를 같이 제출하도록 수정 5. 강제 브라우징 6. 디렉터리 접근 공격
  • 설계/실행 오작동이나 보안 문제 막기 위한 대책 - 뒤로 가기 버튼 대책 - 브라우저 캐시 무효화 - 뒤로가기 버튼 무효화 - 일회용 토큰 사용 - 이중 폼 제출 대책 - 자바 스크립트로 폼 제출 - 일회용 토큰 사용 - 디버그 정보 출력 X - 전역 변수에 정보 담지 않기 (Stateless 하게 유지)

HTTP 서버 구현하기 학습테스트 [입출력(I/O) 스트림]

  • 참고 1: https://develop-im.tistory.com/54
  • 참고 2: https://lannstark.tistory.com/34
  • 자바에서는 Stream을 통해 데이터 입출력 - 데이터 입력 받을 때: InputStream - 데이터 출력할 때: OutputStream - NW 상의 다른 프로그램과 데이터 교환하려면 양쪽 모두 입력/출력 스트림 필요 - 단일 방향으로 연속적으로 흘러감 - Java.io 패키지에서 기본적인 데이터 입출력 제공 - 파일 시스템의 정보를 얻기 위한 File 클래스와 데이터 입출력 담당 Stream 클래스 제공 - File: 파일 시스템의 파일 정보 얻기 위한 클래스 - Console: 콘솔로부터 문자 입출력하기 위한 클래스 - InputStream/OutputStream: "바이트 단위" 입출력을 위한 최상위 입출력 스트림 클래스 => 그림/멀티미디어/문자 등의 모든 종류 - FileInputStream/FileOutputStream - DataInputStream/DataOutputStream - ObjectInputStream/ObjectOutputStream - BufferedInputStream/BufferedOutputStream - PrintStream - Reader/Writer: "문자 단위" 입출력을 위한 최상위 입출력 스트림 클래스 => 오로지 문자만 - FileReader/FileWriter - InputStreamReader/InputStreamWriter - BufferedReader/BufferedWriter - PrinterWriter
  • InputStream - 바이트 기반 입력 스트림의 최상위 클래스 (추상 클래스) - 파일 데이터를 읽거나 - 네트워크 소켓을 통해 데이터를 읽거나 - 키보드에서 입력한 데이터 읽거나 - InputStream은 통로이기에, read를 통해 읽은 것은 다시 되돌아가 읽기 X - int read(): 한 바이트(8bit, 고로 -128~127) 를 읽어 int(0~255)로 반환, 반환할 수 없다면 -1 - byte[] readAllBytes(): inputStream의 모든 바이트 다 읽어 byte 배열로 반환 - ByteArrayInputStream - 바이트를 내부 버퍼에 보관/유지 - BufferedInputStream - 참고: https://m.blog.naver.com/highkrs/220510982995 - 바이트 단위로 파일을 읽어올때 사용 - 버퍼..? - 프로그램이 기억장치에 접근해서 파일 가져오는 건 속도 느려져 - 데이터 읽어 올때 한번에 읽어 버퍼에 두거나, 데이터를 쓸 때 버퍼에 써두고 한번에 출력하는 방식 채택 - 내부에 디폴트로 8192 바이트의 버퍼를 두고 작업하기에 속도가 빨라짐
    public class BufferedInputStream extends FilterInputStream {
        private static int DEFAULT_BUFFER_SIZE = 8192;
    }
    
      - 속도를 중요시한다면 사용할 것!
    
  • OutputStream - 바이트 기반 출력 스트림의 최상위 클래스 (추상 클래스) - 파일로 보내거나 - 메모리로 보내거나 - 네트워크로 보내거나 - close(): OutputStream을 닫는다 - flush(): 버퍼에 남아있는 출력 스트림을 출력 - write(): 버퍼의 내용을 출력 - ByteArrayOutputStream - 바이트를 내부 버퍼에 보관/유지 - BufferedOutputStream - 데이터를 쓰기 위해서는 버퍼로 지정한 만큼의 바이트가 스트림에 모여야 보조기억장치에 씀
  • FilterStream - FilterInputStream: InputStream의 메서드 활용하면서 추가적인 장치들 제공하도록 - BufferedInputStream의 부모클래스 - DataInputStream의 부모클래스 - PushbackInputStream의 부모클래스 - FilterOutputStream: OutputStream도 비슷
  • Reader, Writer - 문자 기반 입력 스트림의 최상위 추상 클래스 - int read(): 입력 스트림으로부터 한 개 문자 읽고 리턴 - 한개의 문자를 읽고 int 타입으로 리턴 - 4byte 중 끝 2byte에만 데이터 들어있음 - int read(char[] cbuf): 입력 스트림으로부터 읽은 문자들을 cbuf에 저장, 실제 읽은 문자수 리턴 - InputStreamReader - 바이트 단위의 InputStream을 통해 데이터를 읽어들여 문자단위로 읽는 방식으로 변형한다 - 그러다보니 생성자에서 인코딩을 설정해 줄 수 있음 - InputStreamReader(InputStream in, String enc) - BufferedReader - 이것도 버퍼 사용 - 엔터를 경계로 인식하고, String으로 인식한 데이터 고정
    @Test
    void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
        final String emoji = String.join("\r\n",
                "😀😃😄😁😆😅😂🤣🥲☺️😊",
                "😇🙂🙃😉😌😍🥰😘😗😙😚",
                "😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
                "");
        final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
        final Reader reader = new InputStreamReader(inputStream);
        final BufferedReader bufferedReader = new BufferedReader(reader);
    }
    
      - 그러다 보니 해당 학습테스트에서 개행이 일어날 때마다 한줄씩 읽어올 수 있었구나!
    
  • try-with-resources - 참고: https://ryan-han.com/post/java/try_with_resources/ - try에 자원 객체 전달하면, try 블록 끝나면 자동으로 자원 close() 해주는 기능 - try에 전달할 객체는 AutoClosable 인터페이스 구현했어야함 - 스트림 기반 객체들은 해당 방식으로 자원 해제 가능
    public static String getHTML(String url) throws IOException {
        URL targetUrl = new URL(url);
    
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(targetUrl.openStream()))) {
            StringBuffer html = new StringBuffer();
            String tmp;
    
            while ((tmp = reader.readLine()) != null) {
                html.append(tmp);
            }   
            return html.toString();
        }   
    }
    

HTTP 서버 구현하기 학습테스트 [파일 처리]

  • ClassLoader - JVM에서 클래스파일(바이트코드)를 읽고, 클래스 정보를 메모리의 힙/메서드 영역에 저장하는 역할 - Loading > Linking > Initialization 순으로 수행 - JVM은 기본적으로 3가지 종류의 클래스 로더 제공 - Bootstrap ClassLoader: 코어 자바 API - Extension ClassLoader: 시스템 프로퍼티 - Application ClassLoader: 어플리케이션 단에서 저장된 클래스
  • File - 참고: http://www.devkuma.com/books/pages/82 - 자바는 플랫폼 독립적: OS에 상관없이 구동되기에 OS에 따라 동작해야함 - 경로 및 파일 간의 구분자가 다르기에 문자열 직접 입력하기 보다는 File 클래스 변수를 사용해야 함 - File 생성자 - File(String pathname) - File(String parent, String child): parent 디렉토리 경로의 child 파일에 대한 객체 생성 - File(File parent, String child): 파일 객체 parent의 child 파일에 대한 객체 생성 - File(URI uri) - File 메서드 - File getAbsoluteFile(): 파일의 절대 경로를 반환 - String getName(): 폴더나 파일의 이름을 넘겨줌 - File 조작 관련 메서드 - boolean delete(): 파일이나 디렉토리 삭제 - boolean mkdir(): 해당 경로에 폴더 만듦 - File 체크 관련 메서드 - boolean exists(): 파일 존재 여부 반환 - boolean isFile(): 해당 경로가 file 인지 여부 반환 - File 권한 관련 메서드 - boolean canExecute(): 실행 권한 여부 - boolean canRead(): 읽기 권한 여부 - boolean canWrite(): 쓰기 권한 여부
  • Files - 참고: https://codevang.tistory.com/159 - Static 메서드로 구성된 클래스 - Path 객체를 사용해 피일 시스템 작업 수행 - 메서드 - boolean isDirectory(Path p): 폴더인지 아닌지 검사 - boolean exists(Path p): 파일이 실제 존재하는지 검사 - Path createDirectory(Path p): 디렉토리 생성 - Path createFile(Path p): 파일 생성 - long copy(Path source, Path dest, CopyOption) - Path move(Path source, Path dest, CopyOption) - boolean delete(): 파일 삭제
  • Path - Paths.get("경로")를 통해 생성 - path 메서드 - getRoot(): 루트 디렉토리 - getParent(): 부모 경로
  • URLDecoder - URLEncoder 클래스는 OS 별로 일부 문자를 인식하는 방식이 다름 - 변환 규칙 - 아스키문자 => 그대로 전달 - 공백문자('') => + 기호로 변환
  • Java String의 기본 인코딩은? - 자바의 기본 문자열은 UTF-16 유니코드 인코딩 - InputStreamReader를 활용하면 유니코드 문자로 변환 가능

HTTP 서버 구현하기 학습테스트 [쓰레드]

  • 참고: https://codechacha.com/ko/java-executors/
  • "java.util.concurrent.Executors"와 "java.util.concurrent.ExecutorService"를 활용하면 간단히 쓰레드풀을 생성해 병렬처리 가능
  • ExecutorService - Executors는 ExecutorService 객체를 생성 - Executors.newFixedThreadPool(int): 인자 개수만큼 고정된 쓰레드풀 만듦 - Executors.newCachedThreadPool(): 필요할 때 필요한 만큼 쓰레드풀 생성 - Executors.newScheduledThreadPool(int): 일정 시간뒤에 실행되는 작업이 있다면 활용 생각 - Executors.newSingleThreadExecutor(): 쓰레드 1개인 ExecutorService 리턴 - submit(() -> {}) - 멀티쓰레드로 처리할 작업 예약 - 인자로 람다식 전달 - 예약과 동시에 먼저 생성된 4개의 쓰레드는 작업들을 처리 - Runnable 전달 (return 값이 없음)
    @Test
    void executorSubmit() throws InterruptedException {
        final ExecutorService executor = Executors.newFixedThreadPool(4);
    
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println("Job1 = " + threadName);
        });
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println("Job2 = " + threadName);
        });
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println("Job3 = " + threadName);
        });
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println("Job4 = " + threadName);
        });
    
        // 더 이상 ExecutorService에 Task 추가 X
        // 작업 완료시 쓰레드풀 종료
        executor.shutdown();
    
        if (executor.awaitTermination(20, TimeUnit.SECONDS)) {
            System.out.println(LocalTime.now() + "All jobs done!");
        } else {
            System.out.println(LocalTime.now() + "Some jobs left!");
            executor.shutdownNow();
        }
    }
    

    - execute() - Runnable 전달 (return 값 없음) - Execute과 Callback 1. 작업 후 종료 (execute) - void execute(Runnable command); 2. 작업 후 결과 보고 (callback)

    <T> Future<T> submit(Callable<T> task);
    Future<?> submit(Runnable task);
    

  • CountDownLatch - 고급 언어 대부분 Concurrency에 관련된 API 제공
    @Test
    void testCounterWithConcurrency() throws InterruptedException {
        int numberOfThreads = 10;
        ExecutorService service = Executors.newFixedThreadPool(10);
    
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        MyCounter counter = new MyCounter();
    
        for (int i = 0; i < numberOfThreads; i++) {
            service.execute(() -> {
                counter.increment();
                latch.countDown();
            });
        }
        System.out.println("latch.getCount() = " + latch.getCount()); // 10일수도, 4일수도, 1일수도
        latch.await();
        System.out.println("latch.getCount() = " + latch.getCount()); // 0
        assertThat(counter.getCount()).isEqualTo(numberOfThreads);
    }
    

    - await() - latch를 통해서 병행성 파악 - await()을 통해서 해당 카운트가 다 떨어져 0으로 가는지 파악 -