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)
- 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으로 가는지 파악 -