1. 명령형 프로그래밍 vs 선언형 프로그래밍

 1) 명령형 프로그램

  • 예문
public class ImperativeProgrammingExample {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum = 0;
        for(int number : numbers){
            if(number > 4 && (number % 2 == 0)){
                sum += number;
            }
        }
        System.out.println(sum);
    }
}
  • List에 포함된 숫자들을 for문을 이용해서 순차적으로 접근한다
  • if 문으로 특정 조건에 맞는 숫자들만 sum 변수에 더해서 합계를 구한다
  • 코드가 어떤식으로 실행되어야 하는지에 대한 구체적인 로직들이 코드 안에 그대로 나타난다

 2) 선언형 프로그램

  • 예문
public class DeclarativeProgramingExample {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum =
                numbers.stream()
                        .filter(number -> number > 4 && (number % 2 == 0))
                        .mapToInt(number -> number)
                        .sum();
        System.out.println("# 선언형 프로그래밍: " + sum);
    }
}
  • Java에서 선언형 프로그래밍 방식을 이해하기 위한 가장 적절한 예는 바로 Java 8부터 지원하는 Stream API이다
  • List에 포함된 숫자들을 처리하는 것은 명령형 코드와 동일하지만 처리 방식은 전혀 다르다
  • Java Stream API를 사용하기 때문에 코드 상에 보이지 않는 내부 반복자가 명령형의 for문을 대체하고 있다
  • filter() 메서드(Operation)가 if 문을 대신해서 조건에 만족하는 숫자를 필터링하고 있다
  • Stream API를 사용하는 것이 선언형 프로그래밍 방식인 이유는
    - 명령형 프로그래밍 방식으로 작성된 코드는 위에서 아래로 순차적으로 실행한다
     : for문을 만나게 되면 for문 내부로 진입하게 된다
     : if문을 만나면 if 문의 조건을 판단한 후에 조건에 맞으면 sum 변수에 숫자를 더한 뒤에 다시 for 문을 반복한다
    - Stream API를 사용한 선언형 코드는 numbers.stream().filter().mpaToInt().sum()과 같은 메서드 체인이 순차적으로 실행이 되지 않는다
     : filter()나 mapToInt() 메서드에 파라미터를 전달하기 때문에 즉시 호출되는 것처럼 생각될 수 있지만 그렇지 않다
     : Stream의 경우 최종 연산을 수행하는 메서드를 호출하지 않으면 앞에서 작성한 메서드 체인들이 실행 되지 않는다
     : Stream의 메서드 체인(중간 연산)에는 작업을 해 달라고 선언(요청)하는 람다 표현식만 넘겨준다
     : 최종 연산이 호출될 때 전달 받은 람다 표현식을 기반으로 동작을 수행한다
  • 선언형 프로그래밍 방식은 개발자가 로직을 모두 작성하지 않는다
  • 필요한 동작들을 람다 표현식으로 정의(선언)하고 구체적인 동작 수행은 Operation(연산) 메서드 체인에 위임한다

 

2. 리액티브 프로그래밍의 구조

 1) 예제 1

package com.codestates.example;

import reactor.core.publisher.Mono;

public class HelloReactiveExample01 {
    public static void main(String[] args) {
        Mono<String> mono = Mono.just("Hello, Reactive");
        mono.subscribe(message -> System.out.println(message));
    }
}
  • Publisher는 데이터를 emit하는 역할을 하고 Subsciber는 Publisher가 emit한 데이터를 전달 받아서 소비하는 역할을 한다
    - Puhlisher의 역할을 하는 것이 Mono 이다
    - Subscriber의 역할을 하는 것이
     : subscribe() 메서드 내부에 정의된 람다 표현식인 message ->System.out.println(message) 이다
  • Java의 Stream에서 메서드 체인 형태로 사용할 수 있듯이 리액티브 프로그래밍에서도  메서드 체인을 구성할 수 있다

 2) 예제 2

package com.codestates.example;

import reactor.core.publisher.Mono;

public class HelloReactiveExample02 {
    public static void main(String[] args) {
        Mono
                .just("Hello, Reactive")
                .subscribe(message -> System.out.println(message));
    }
}
  • 예제1의 코드를 하나의 메서드 체인 형태로 표현한 코드이다
  • Java의 Stream API와 리액티브 프로그래밍이 선언형 프로그래밍 방식으로 구성되는 공통점을 가지고 있지만 차이점이 더 많이 존재한다

 3) 예제 3

package com.codestates.example;

import org.springframework.http.converter.json.GsonBuilderUtils;
import reactor.core.publisher.Flux;

import java.util.List;

public class ReactiveGlossaryExample {
    public static void main(String[] args) {
        Flux
                .fromIterable(List.of(1,3,6,7,8,11))
                .filter(number -> number > 4 && (number % 2 == 0))
                .reduce((n1, n2) -> n1 + n2)
                .subscribe(System.out::println);
    }
}

 

 3-1) 리액트 프로그램밍 주요 용어

  • Publisher
    - 데이터를 내보내는 주체를 의미한다
    - 예제3에서는 Flux가 Publisher이다

 

  • Emit 
    - Publisher가 데이터를 내보내는 것을 Emit 이라고 한다

 

  • Subscriber
    - Publisher가 emit한 데이터를 전달 받아서 소비하는 주체를 의미한다
    - 예제3에서는 subscribe(System.out::println) 중에서 System.out::println이 Subscriber에 해당된다
    - 람다 표현식을 메서드 레퍼런스로 축약하지 않았다면 람다 표현식 자체가 Subscriber에 해당된다

 

  • Subscribe
    - 구독을 의미한다
    - subscribe를 사용하면 시스템은 자동으로 subscriber를 생성한다
    - 예제3에서 subscribe() 메서드를 호출하면 subscriber를 생성하여 Publisher가 보내 온 데이터를 출력하게 된다

 

  • Signal
    - Publisher가 발생시키는 이벤트를 의미한다
    - 예제3에서 subscribe() 메서드가 호출되면 Publisher인 Flux는 숫자 데이터를 하나씩 하나씩 emit 한다
    - 숫자 데이터를 하나씩 emit하는 자체를 리액티브 프로그래밍에서는 이벤트가 발생하는 것으로 간주한다
    - 이벤트 발생을 다른 컴포넌트에게 전달하는 것을 Signal을 전송한다라고 표현한다

 

  • Operator
    - 리액티브 프로그래밍에서 어떤 동작을 수행하는 메서드를 의미한다
    - 예제3에서 fromIterable(), filter(), reduce() 등의 메서드 하나 하나를 Operator라고 한다
    - 연산자라고도 한다

 

  • Sequence
    - Operator 체인으로 표현되는 데이터의 흐름을 의미한다
    - 예제3에서 Operator 체인으로 작성된 코드 자체를 하나의 Sequence라고 볼 수 있다

 

  • Upstream / Downstream
    - Sequence 상의 특정 Operator를 기준으로 위쪽의 Sequence 일부를 Upstream이라고 한다
    - 아래 쪽 Sequence 일부를 Downstream이라고 한다
    - 예제3에서 filter() Operaotr를 기준에서 보면 filter() Operator 위 쪽의 fromIterable()은 Upstream 이다
    - filter() Operator 아래 쪽의 reduce() Operator는 Downstream 이다

 

▶ declarative programming : https://www.techtarget.com/searchitoperations/definition/declarative-programming 

 

What Is Declarative Programming? Definition from SearchITOperations

Declarative programming focuses on the outcome, not the journey. Learn how it works and how it compares to imperative programming through examples.

www.techtarget.com

 Declarative programming : https://en.wikipedia.org/wiki/Declarative_programming 

 

Declarative programming - Wikipedia

Programming paradigm based on modeling the logic of a computation In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation witho

en.wikipedia.org

 

1. 리액티브 프로그래밍(Reactive Programming)

 1) 사전 준비

 2) 리액티브

  • 일반적으로 ‘반응을 하는’, ‘반응을 보이는’이란 의미이다
  • 리액티브(Reactive)에 대한 빠른 이해를 위한 단어로 리액션(Reaction)이 있다

 

 3) 리액티브 시스템(Reactive System)

  • 반응을 잘하는 시스템을 의미한다
  • 리액티브 시스템을 이용하는 클라이언트의 요청에 반응을 잘하는 시스템이다
  • 리액티브 시스템 관점에서의 반응은 쓰레드의 Non-Blocking과 관련이 있다
    - 리액티브 시스템은 클라이언트의 요청에 대한 응답 대기 시간을 최소화 할 수 있도록 쓰레드가 차단되지 않게 한다
    - Non-Blocking을 통해 클라이언트에게 즉각적으로 반응하도록 구성된 시스템이다
  • 리액티브 선언문에 나와 있는 리액티브 시스템 설계원칙의 그림을 통해 리액티브 시스템의 특징을 확인해 봅시다.
    - MEANS
      : 리액티브 시스템에서 사용하는 커뮤니케이션 수단이다
      : Message Driven
        → 리액티브 시스템에서는 메시지 기반 통신을 통해 여러 시스템 간에 느슨한 결합을 유지한다
    - FORM
     : 메시지 기반 통신을 통해 리액티브 시스템이 어떤 특성을 가지는 구조로 형성되는지를 의미한다
     : Elastic
        → 시스템으로 들어오는 요청량이 크기에 상관없이 일정한 응답성을 유지한다
     : Resillient
        → 시스템의 일부분에 장애가 발생하더라도 응답성을 유지한다
    - VALUE
     : 리액티브 시스템의 핵심 가치가 무엇인지를 표현하는 영역이다
     : Responsive
        → 리액티브 시스템은 클라이언트의 요청에 즉각적으로 응답할 수 있어야 한다
     : Maintainable
        → 클라이언트의 요청에 대한 즉각적인 응답이 지속가능해야 한다
     : Extensible
        → 클라이언트의 요청에 대한 처리량을 자동으로 확장하고 축소할 수 있어야 한다

 4) 리액티브 프로그래밍(Reactive Programming)

  • 리액티브 시스템에서 사용되는 프로그래밍 모델이다
  • 리액티브 시스템에서의 메시지 기반 통신(Message Driven)은 Non-Blocking 통신과 유기적인 관계를 맺고 있다
  • 리액티브 프로그래밍은 Non-Blocking 통신을 위한 프로그래밍 모델이다

 

3. 리액티브 프로그래밍의 특징

  • 선언형 프로그래밍 방식을 사용하는 대표적인 프로그래밍 모델이다
  • 지속적으로 데이터가 입력으로 들어올 수 있고 데이터에 어떤 변경이 발생하는 이벤트가 발생할 때 마다 데이터를 계속해서 전달한다
  • 지속적으로 발생하는 데이터를 하나의 데이터 플로우로 보고 데이터를 자동으로 전달합니다.
  • 리액티브 스트림즈(Reactive Streams)
    - JPA와 JDBC API의 공통점은
     : JPA(Java Persistence API)는 Java 진영에서 사용하는 ORM(Object-Relational Mapping) 기술의 표준 사양(또는 명세, Specification)이다
     : JDBC는 Java 애플리케이션에서 데이터베이스에 액세스하기 위한 표준 사양(또는 명세, Specification)이다
    - 리액티브 스트림즈(Reactive Streams)는 리액티브 프로그래밍을 위한 표준 사양(또는 명세, Specification)이다

 

4. 리액티브 스트림즈 컴포넌트(구성요소)

 1) Publisher 인터페이스

public interface Publisher<T> {
    public void subscribe(Subscriber<? super T> s);
}
  • 데이터 소스로 부터 데이터를 내보내는(emit) 역할을 한다
  • subscribe() 추상 메서드를 포함하고 있다
  • subscribe()의 파라미터로 전달되는 Subscriber가 Publisher로부터 내보내진 데이터를 소비하는 역할을 한다
  • subscribe() 는 Publisher가 내보내는 데이터를 수신할 지 여부를 결정하는 구독의 의미를 가지고 있다
  • 일반적으로 subscribe()가 호출되지 않으면 Publisher가 데이터를 내보내는 프로세스는 시작되지 않는다

 

 2) Subscriber 인터페이스

public interface Subscriber<T> {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}
  • Publisher로부터 내보내진 데이터를 소비하는 역할을 한다
  • Subscriber는 네 개의 추상 메서드를 포함하고 있다
    - onSubscribe(Subscription s)
     : 구독이 시작되는 시점에 호출된다
     : onSubscribe() 내에서 Publisher에게 요청할 데이터의 개수를 지정하거나 구독 해지 처리를 할 수 있다
    - onNext(T t)
     : Publisher가 데이터를 emit할 때 호출된다
     : emit된 데이터를 전달 받아서 소비할 수 있다
    - onError(Throwable t)
     : Publisher로부터 emit된 데이터가 Subscriber에게 전달되는 과정에서 에러가 발생할 경우에 호출된다
    - onComplete()
     : Publisher가 데이터를 emit하는 과정이 종료될 경우 호출된다
     : 데이터의 emit이 정상적으로 완료 된 후, 처리해야 될 작업이 있다면 onComplete() 내에서 수행할 수 있다

 

 3) Subscription 인터페이스

public interface Subscription {
    public void request(long n);
    public void cancel();
}
  • Subscriber의 구독 자체를 표현한 인터페이스이다
    - request(long n)
     : Publihser가 emit하는 데이터의 개수를 요청한다
    - cancel()
     : 구독을 해지하는 역할을 한다
     : 구독 해지가 발생하면 Publisher는 더이상 데이터를 emit하지 않는다

 

 4) Processor 인터페이스

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
  • Subscriber 인터페이스와 Publisher 인터페이스를 상속하고 있다
    - Publisher와 Subscriber의 역할을 동시에 할 수 있는 특징을 가진다
    - 별도로 구현해야 되는 추상 메서드는 없다

 

5. 리액티브 스트림즈 구현체

 1) Project Reactor(Reactor)

  • 리액티브 스트림즈를 구현한 대표적인 구현체이다
  • Spring과 가장 잘 맞는 리액티브 스트림즈 구현체이다
  • Reactor는 Spring 5의 리액티브 스택에 포함되어 있ek
  • Sprig Reactive Application 구현에 있어 핵심적인 역할을 담당한다

 2) RxJava

  • .NET 기반의 리액티브 라이브러리를 넷플릭스에서 Java 언어로 포팅한 JVM 기반의 리액티브 확장 라이브러리이다
  • RxJava의 경우 2.0부터 리액티브 스트림즈 표준 사양을 준수하고 있다
  • 이 전 버전의 컴포넌트와 함께 혼용되어 사용이 되고 있다

 3) Java Flow API

  • Java 역시 Java 9부터 리액티브 스트림즈를 지원한다
  • Flow API는 리액티브 스트림즈를 구현한 구현체가 아니라 리액티브 스트림즈 표준 사양을 Java 안에 포함을 시킨 구조라고 볼 수 있다
  • 다양한 벤더들이 JDBC API를 구현한 드라이버를 제공할 수 있도록 SPI(Service Provider Interface) 역할을 하는것 처럼 Flow API 역시 리액티브 스트림즈 사양을 구현한 여러 구현체들에 대한 SPI 역할을 한다

 4) 기타 리액티브 확장(Reactive Extension)

  • RxJava의 Rx는 Reactive Extension의 줄임말이다
  • 이 의미는 특정 언어에서 리액티브 스트림즈를 구현한 별도의 구현체가 존재한다는 의미이다
  • 실제로 다양한 프로그래밍 언어에서 리액티브 스트림즈를 구현한 리액티브 확장(Reactive Extension) 라이브러리를 제공하고 있다
  • 대표적인 리액티브 확장(Reactive Extension) 라이브러리로 앞에서 언급한 RxJava가 있다
  • 이외에도 RxJS, RxAndroid, RxKotlin, RxPython, RxScala 등이 있다

 

The Reactive Manifesto : https://www.reactivemanifesto.org/glossary 

 

Glossary - The Reactive Manifesto

Glossary Asynchronous The Oxford Dictionary defines asynchronous as “not existing or occurring at the same time”. In the context of this manifesto we mean that the processing of a request occurs at an arbitrary point in time, sometime after it has been

www.reactivemanifesto.org

 Reactive Streams : https://github.com/reactive-streams/reactive-streams-jvm 

 

GitHub - reactive-streams/reactive-streams-jvm: Reactive Streams Specification for the JVM

Reactive Streams Specification for the JVM. Contribute to reactive-streams/reactive-streams-jvm development by creating an account on GitHub.

github.com

▶ ReactiveX : https://reactivex.io/ 

 

ReactiveX

CROSS-PLATFORM Available for idiomatic Java, Scala, C#, C++, Clojure, JavaScript, Python, Groovy, JRuby, and others

reactivex.io

 

'Spring WebFlux' 카테고리의 다른 글

Spring WebFlux - 리액티브 프로그래밍 기본구조  (0) 2022.08.10

https://www.w3schools.com/java/java_user_input.asp

 

Java User Input (Scanner class)

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

1. Scanner

  • Scanner 클래스 는 Scanner 사용자 입력을 받는 데 사용되며 java.util패키지에서 찾을 수 있다.
  • Scanner 클래스 를 사용하려면 Scanner클래스의 개체를 만들고 Scanner클래스 메서드를 사용한다 
  • 예문은 nextLine() 메서드를 사용하여 문자열을 입력받는다
import java.util.Scanner;  // Import the Scanner class

class Main {
  public static void main(String[] args) {
    Scanner myObj = new Scanner(System.in);  // Create a Scanner object
    System.out.println("Enter username");

    String userName = myObj.nextLine();  // Read user input
    System.out.println("Username is: " + userName);  // Output user input
  }
}


Enter username

 

 1) 입력 유형

Method
Description
nextBoolean() Reads a boolean value from the user
nextByte() Reads a byte value from the user
nextDouble() Reads a double value from the user
nextFloat() Reads a float value from the user
nextInt() Reads a int value from the user
nextLine() Reads a String value from the user
nextLong() Reads a long value from the user
nextShort() Reads a short value from the user
import java.util.Scanner;

class Main {
  public static void main(String[] args) {
    Scanner myObj = new Scanner(System.in);

    System.out.println("Enter name, age and salary:");

    // String input
    String name = myObj.nextLine();

    // Numerical input
    int age = myObj.nextInt();
    double salary = myObj.nextDouble();

    // Output input by user
    System.out.println("Name: " + name);
    System.out.println("Age: " + age);
    System.out.println("Salary: " + salary);
  }
}



Enter name, age and salary:

 

참고: 잘못된 입력(예: 숫자 입력의 텍스트)을 입력하면 예외/오류 메시지(예: "InputMismatchException")가 표시된다

 

2. BufferReader

Scanner 클래스는 소량의 문자열을 입력받는데 용이하다

BufferReader 클래스는 용량이 많은 문자열을 입력받는데 적합하다
- 문자열을 하나식 전달하지 않고 버퍼에 담아 두었다가 한번에 전달한다
- 속도가 빠르고 효율적이다

BufferReader 는 Enter만을 경계로 입력값을 인식한다
- 입력값을 나누는 가공 작업이 추가로 필요하다

public class String_BufferedReader {
    public static void main(String[] args) throws Exception {
        //BufferReader를 사용하여 readLine()로 입력받을 경우 예외처리가 필수이다

        //BufferReader (InputStreamReader) : 문자열 입력 메서드
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //InputStreamReader : 문자열 입력 메서드

        String str = br.readLine();  //String 문자열을 라인 단위로 입력, Enter를 경계로 한다

        String arr[] = str.split(" ");  //" "빈칸을 경계로 하여 배열로 변환한다

        int str1 = Integer.parseInt(br.readLine());  //정수형의 문자열 입력, 형변환이 필요하다

//        System.out.println(str);
        System.out.println(arr);
        System.out.println(str1);


        //BufferWriter (OutputStreamWriter) : 문자열 출력 메서드
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        bw.newLine();  //줄바꿈 , "\n"와 같다
        bw.write(str + "\n");  //출력
        bw.newLine(); //줄바꿈 , "\n"와 같다
        bw.flush();//남아있는 데이터를 모두 출력
        bw.close();//스트림 닫음
    }

    // 문자열 가공 방법
    public void manufacturing() throws Exception {
        // 1번째 방법) StringTokenizer를 이용한다.
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine()); //StringTokenizer인자값에 입력 문자열 넣음
        int a = Integer.parseInt(st.nextToken()); //첫번째 호출을 to int
        int b = Integer.parseInt(st.nextToken()); //두번째 호출을 to int

        // 두번째 방법) string을 한번에 받아서 split을 이용해 배열로 변환
        String s = br.readLine();
        String array[] = s.split(" "); //공백마다 데이터 끊어서 배열에 넣음

        return;
    }
}
> Task :classes
 bw.newLine();
123456789

> Task :String_BufferedReader.main()
[Ljava.lang.String;@2d363fb3
123456789

 bw.newLine();



'JAVA' 카테고리의 다른 글

Java - 문자열 바꾸기 - replace  (0) 2022.09.13
Java - Static  (0) 2022.08.14
Java - 날짜(Date), 시간(Time)  (0) 2022.08.08
Java - 제어문(Control Flow Statements)  (0) 2022.06.02
Java - 콘솔 입출력(I/O)  (0) 2022.06.02

https://www.w3schools.com/java/java_date.asp 

 

Java Date and Time

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

Class DescriptionDescription
LocalDate Represents a date (year, month, day (yyyy-MM-dd))
LocalTime Represents a time (hour, minute, second and nanoseconds (HH-mm-ss-ns))
LocalDateTime Represents both a date and a time (yyyy-MM-dd-HH-mm-ss-ns)
DateTimeFormatter Formatter for displaying and parsing date-time objects

 

1. 현재 날짜 표시

  • 현재 날짜를 표시하려면 java.time.LocalDate클래스를 가져오고 해당 now()메서드를 사용합니다.
import java.time.LocalDate; // import the LocalDate class

public class Main {
  public static void main(String[] args) {
    LocalDate myObj = LocalDate.now(); // Create a date object
    System.out.println(myObj); // Display the current date
  }
}


2022-08-08

 

2. 현재 시간 표시

  • 현재 시간(시, 분, 초 및 나노초)을 표시하려면 java.time.LocalTime클래스를 가져오고 해당 now()메서드를 사용합니다.
import java.time.LocalTime; // import the LocalTime class

public class Main {
  public static void main(String[] args) {
    LocalTime myObj = LocalTime.now();
    System.out.println(myObj);
  }
}


13:54:38.126484

 

 

3. 현재 날짜 및 시간 표시

  • 현재 날짜와 시간을 표시하려면 java.time.LocalDateTime클래스를 가져오고 해당 now()메서드를 사용합니다.
import java.time.LocalDateTime; // import the LocalDateTime class

public class Main {
  public static void main(String[] args) {
    LocalDateTime myObj = LocalDateTime.now();
    System.out.println(myObj);
  }
}


2022-08-08T13:54:38.126501

 

4. 날짜 및 시간 형식 지정

  • 위의 예에서 "T"는 날짜와 시간을 구분하는 데 사용됩니다. DateTimeFormatter동일한 패키지 의 메서드와 함께 클래스를 사용하여 ofPattern()날짜-시간 개체의 형식을 지정하거나 구문 분석할 수 있습니다. 다음 예는 날짜-시간에서 "T"와 나노초를 모두 제거합니다.
import java.time.LocalDateTime; // Import the LocalDateTime class
import java.time.format.DateTimeFormatter; // Import the DateTimeFormatter class

public class Main {
  public static void main(String[] args) {
    LocalDateTime myDateObj = LocalDateTime.now();
    System.out.println("Before formatting: " + myDateObj);
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

    String formattedDate = myDateObj.format(myFormatObj);
    System.out.println("After formatting: " + formattedDate);
  }
}


Before Formatting: 2022-08-08T13:54:38.126996
After Formatting: 08-08-2022 13:54:38

 

5. ofPattern()

  • 다른 형식으로 날짜와 시간을 표시하려는 경우 이 메서드는 모든 종류의 값을 허용합니다
Value Example
yyyy-MM-dd "1988-09-29"
dd/MM/yyyy "29/09/1988"
dd-MMM-yyyy "29-Sep-1988"
E, MMM dd yyyy "Thu, Sep 29 1988"
import java.time.LocalDateTime;  // Import the LocalDateTime class
import java.time.format.DateTimeFormatter;  // Import the DateTimeFormatter class

public class Main {
  public static void main(String[] args) {  
    LocalDateTime myDateObj = LocalDateTime.now();  
    System.out.println("Before formatting: " + myDateObj);  
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");  
    
    String formattedDate = myDateObj.format(myFormatObj);  
    System.out.println("After formatting: " + formattedDate);  
  }  
}  



Before Formatting: 2022-08-08T14:05:58.945620
After Formatting: 08-08-2022 14:05:58
import java.time.LocalDateTime;  // Import the LocalDateTime class
import java.time.format.DateTimeFormatter;  // Import the DateTimeFormatter class

public class Main {
  public static void main(String[] args) {  
    LocalDateTime myDateObj = LocalDateTime.now();  
    System.out.println("Before Formatting: " + myDateObj);  
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");  
    
    String formattedDate = myDateObj.format(myFormatObj);  
    System.out.println("After Formatting: " + formattedDate);  
  }  
}  



Before Formatting: 2022-08-08T14:06:58.846495
After Formatting: 08/08/2022 14:06:58
import java.time.LocalDateTime;  // Import the LocalDateTime class
import java.time.format.DateTimeFormatter;  // Import the DateTimeFormatter class

public class Main {
  public static void main(String[] args) {  
    LocalDateTime myDateObj = LocalDateTime.now();  
    System.out.println("Before Formatting: " + myDateObj);  
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss");  
    
    String formattedDate = myDateObj.format(myFormatObj);  
    System.out.println("After Formatting: " + formattedDate);  
  }  
}  



Before Formatting: 2022-08-08T14:07:39.733365
After Formatting: 08-Aug-2022 14:07:39
import java.time.LocalDateTime;  // Import the LocalDateTime class
import java.time.format.DateTimeFormatter;  // Import the DateTimeFormatter class

public class Main {
  public static void main(String[] args) {  
    LocalDateTime myDateObj = LocalDateTime.now();  
    System.out.println("Before Formatting: " + myDateObj);  
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("E, MMM dd yyyy HH:mm:ss");  
    
    String formattedDate = myDateObj.format(myFormatObj);  
    System.out.println("After Formatting: " + formattedDate);  
  }  
}  



Before Formatting: 2022-08-08T14:08:27.450224
After Formatting: Mon, Aug 08 2022 14:08:27

'JAVA' 카테고리의 다른 글

Java - Static  (0) 2022.08.14
Java - 문자열 입력(Scanner, BufferReader)  (0) 2022.08.08
Java - 제어문(Control Flow Statements)  (0) 2022.06.02
Java - 콘솔 입출력(I/O)  (0) 2022.06.02
Java - 연산자(Operator)  (0) 2022.06.02

1. 배포자동화(Automated Deployment)

  • 한번의 클릭 혹은 명령어 입력으로 전체 배포 과정을 자동으로 진행한다
  • 수동적이고 반복적인 배포 과정을 자동화하여 시간을 절약한다
  • 휴먼 에러(Human Error)를 방지할 수 있다
    - 휴먼 에러란 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수이다
    - 기존의 배포 과정과 비교하여 특정 과정을 생략하거나 다르게 진행하여 오류가 발생하는 것이 휴먼 에러이다
    - 전체 배포 과정을 매번 일관되게 진행하는 구조를 설계하여 휴먼 에러 발생 가능성을 낮출 수 있다

2. 배포 자동화 파이프라인(Pipeline)

 
  • 배포에서 파이프라인(Pipeline)이란 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 말한다
  • 파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리한다
    - 각 단계는 파이프라인 안에서 순차적으로 실행된다
    - 각 단계마다 주어진 작업(Actions)들을 수행한다
  • 파이프라인에서 대표적으로 쓰이는 세 가지 단계의 이름 및 수행하는 작업이다
    - Source 단계
     : 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달한다

    - Build 단계
     : Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공한다
     : Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달한다

    - Deploy 단계
     : Build 단계로부터 전달받은 결과물을 실제 서비스에 반영한다
  • 파이프라인의 단계는 상황과 필요에 따라 더 세분화되거나 간소화될 수 있다

3. AWS 개발자 도구

  • AWS 에는 개발자 도구 섹션이 존재한다
  • 개발자 도구 섹션에서 제공하는 서비스를 활용하여 배포 자동화 파이프라인을 구축할 수 있다

 1) CodeCommit

  • Source 단계를 구성할 때 CodeCommit 서비스를 이용한다
  • CodeCommit은 GitHub과 유사한 서비스를 제공하는 버전 관리 도구이다
  • GitHub과 비교했을 때 CodeCommit 서비스는 보안과 관련된 기능이 강하다
    - 소스 코드의 유출이 크게 작용하는 기업에서는 매우 중요한 요소이다
  • CodeCommit을 사용할 때는 과금 가능성을 고려해야 한다
    - 프리티어 한계 이상으로 사용할 시 사용 요금이 부과될 수도 있다
    - 사이드 프로젝트나 가볍게 작성한 소스 코드를 저장해야 할 경우에는 GitHub을 이용하는 것이 효과적이다

 2) CodeBuild

  • Build 단계에서는 CodeBuild 서비스를 이용한다
  • 유닛 테스트, 컴파일, 빌드와 같은 빌드 단계에서 필수적으로 실행되어야 할 작업들을 명령어를 통해 실행할 수 있다
  • CodeBuild 서비스는 사용자가 작성한 buildspec.yml 파일을 참조하여 작업을 수행한다

 3) CodeDeploy

  • Deploy 단계를 구성할 때는 기본적으로 다양한 서비스를 이용할 수 있다
  • CodeDeploy와 S3 서비스를 이용할 수 있다
  • CodeDeploy 서비스를 이용하면 실행되고 있는 서버 애플리케이션에 실시간으로 변경 사항을 전달할 수 있다
  • S3 서비스를 통해 S3 버킷을 통해 업로드된 정적 웹 사이트에 변경 사항을 실시간으로 전달하고 반영할 수 있다
  • CodeDeploy 서비스는 사용자가 작성한 appspec.yml 파일을 참조하여 작업을 수행한다

 4) CodePipeline

  • 각 단계를 연결하는 파이프라인을 구축할 때 CodePipeline 서비스를 이용한다
  • AWS 프리티어 계정 사용 시 한 계정에 두 개 이상의 파이프라인을 생성하면 추가 요금이 부여될 수 있다

 

※ 참조

▶ CodeBuild의 빌드 사양 참조 : https://docs.aws.amazon.com/ko_kr/codebuild/latest/userguide/build-spec-ref.html 

 

CodeBuild의 빌드 사양 참조 - AWS CodeBuild

를 지정하는 경우runtime-versions: Ubuntu 표준 이미지 2.0 이상 또는 Amazon Linux 2 (AL2) 표준 이미지 1.0 이상 외의 이미지를 사용하는 경우, 빌드에서 경고가 발생합니다.Skipping install of runtimes. Runtime versio

docs.aws.amazon.com

 CodeDeploy AppSpec 파일 참조 : https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file.html 

 

CodeDeploy AppSpec 파일 참조 - AWS CodeDeploy

tar 및 압축된 tar 아카이브 파일 형식(.tar 및 .tar.gz)은 Windows Server 인스턴스에서 지원되지 않습니다.

docs.aws.amazon.com

 


4.  AWS Pipeline을 통한 배포 자동화

 

AWS Code Pipeline

 

  • AWS 개발자 도구 서비스를 이용해서 배포 자동화 파이프라인을 구축한다
    - CodePipeline을 이용해서 각 단계를 연결하는 파이프라인을 구축한다
    - Source 단계에서 소스 코드가 저장된 GitHub 리포지토리를 연결한다
    - Build 단계에서 CodeBuild 서비스를 이용하여 EC2 인스턴스로 빌드된 파일을 전달한다
    - Deploy 단계에서 CodeDeploy 서비스를 이용하여 EC2 인스턴스에 변경 사항을 실시간으로 반영한다
  • 변경 사항을 GitHub 리포지토리에 반영했을 경우, 배포 과정이 자동으로 진행된다
  • 배포 과정에서 오류가 생길 경우, log 파일을 참조하여 문제점을 확인한다
  • 배포한 프로젝트를 Postman을 통해 확인할 수 있다

5. 배포자동화 실습

 

  • 링크에 접속하여 해당 리포지토리를 자신의 리포지토리로 fork 한다
  • git clone 명령어를 사용하여 자신의 로컬 환경에 소스 코드를 저장한다
    - EC2 인스턴스에 소스 코드를 clone 하지 않는다
  • EC2 인스턴스를 준비한다
    - 터미널을 통해 EC2 인스턴스에 접속하여 java, ruby, aws-cli 를 설치하여 개발 환경을 구축한다
    - EC2 인스턴스에 어떤 소스 코드도 clone하지 않는다
    - EC2 인스턴스를 생성하고 개발 환경 구축하는 작업외에 다른 추가 작업은 하지 않는다

 

 1) EC2 인스턴스 개발 환경을 구축 방법 - 추가 사항

  • 우분투 터미널을 실행한다
  • 패키지 매니저가 관리하는 패키지의 정보를 최신 상태로 업데이트하기 위해서 아래 명령어를 터미널에 입력합니다.
sudo apt update

 

  • ubuntu 업데이트 과정이 끝나면 java를 설치한다
sudo apt install openjdk-11-jre-headless

      - 'Do you want to continue?' 확인창이 나올경우 "Y"를 입력한다

      - 리눅스 운영체계에 맞는 jdk 파일을 추가한다

      - 설치 과정 완료 후 java -version 명령어를 입력하여 java 라이브러리가 설치되었는지 확인한다
       : 명령어를 입력했는데 오류가 난다면 java 라이브러리가 정상적으로 설치되지 않은 것이다

 java -version

 


 

최신 버전의 AWS CLI 설치 또는 업데이트 - AWS Command Line Interface

설치 관리자의 아무 위치에서나 Cmd+L을 눌러 설치에 대한 디버그 로그를 볼 수 있습니다. 이렇게 하면 로그를 필터링하고 저장할 수 있는 로그 창이 열립니다. 로그 파일도 /var/log/install.log에 자

docs.aws.amazon.com

홈페이지에서 설치파일을 받아온다
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

받은 파일의 패키지를 확인한다
sudo apt install unzip

패키지의 압축을 푼다
unzip awscliv2.zip

파일을 설치한다
sudo ./aws/install

aws 버젼을 확인한다
aws --version

 


  • EC2 인스턴스에 CodeDeploy Agent를 설치한다
ubuntu 프로그램을 업데이트 한다
sudo apt update

ruby를 설치한다
sudo apt install ruby-full

아래 문구가 나오면 'Y'를 입력한다
Do you want to continue? [Y/n]

 


wget를 설치한다
sudo apt install wget

ubuntu 디렉토리로 이동한다
각자 컴퓨터에 따라 다를 수 있다
cd /home/ubuntu

aws 홈페이지에서 코드배포 파일을 다운받는다
sudo wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

파일, 폴더의 권한을 변경한다
sudo chmod +x ./install
sudo ./install auto > /tmp/logfile

※  Linux에서 "chmod +x" 명령이란 무엇입니까? : https://linuxtect.com/what-is-chmod-x-command-in-linux/ 

 

What Is “chmod +x” Command In Linux? – LinuxTect

Linux provides the chmod command which is used to change file and folder permission. The chmod command is provided by all major Linux distributions like Ubuntu, Debian, CentOS, Mint, Kali, RHEL, SUSE, etc. The chmod command has different options and parame

linuxtect.com

      - 설치가 완료되면 다음 명령어를 이용해 서비스가 실행중인지 확인한다

sudo service codedeploy-agent status

      - 실행중이라는 문구가 뜨지 않는 경우 공식문서를 참고한다
       : Ubuntu Server용 CodeDeploy 에이전트 설치

       : https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html 

 

Ubuntu Server용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

출력을 임시 로그 파일에 쓰는 것은 Ubuntu 20.04에서 install 스크립트를 사용하여 알려진 버그를 해결하는 동안 사용해야 하는 해결 방법입니다.

docs.aws.amazon.com

 


  • 설치가 모두 완료되면 postmam 또는 브라우저를 이용해 테스트를 진행한다
    - 생성한 EC2 인스턴스의 IP 주소를 이용하여 테스트 진행한다

 


 

1. 브라우저 속 게임 화면의 특정 단어 확인

  • 이미지는 sebcontents/part1을 이용한다
    - 태그는 latest 이다
    - sebcontents/part1은 아파치 웹 서버 이미지(httpd) 기반으로 commit된 새로운 이미지이다

 

https://hub.docker.com/r/sebcontents/part1/tags 에서 이미지를 가져온다

docker pull sebcontents/part1

  • Docker Hub에 저장되어 있는 이미지를 사용하는 경우, repository에 작성된 안내 사항을 필히 확인한다
  • sebcontents/part1 이미지의 repository로 이동하여 안내 사항을 참고하고, 컨테이너를 생성한다

  • 컨테이너를 통해 space 게임을 실행한다
    - 절대경로임에 유의한다

  • 127.0.0.1:3000 으로 접속하여 게임을 실행한다

 

 

2. 컨테이너 속 txt 파일 안에 있는 단어 확인

  • docker exec 명령어를 이용해 실습을 진행한다
  • 컨테이너_이름 부분에는 '1. 브라우저...'에서 사용한 컨테이너의 이름을 입력한다
  • 터미널(PowerShell)을 열어 이래 명령어를 입력한다
    - 컨테이너 안에서 bash shell을 실행하는 명령이다
    - git bash에서는 에러가 발생한다 
docker exec -it 컨테이너_이름 bash
  • root@ 다음에 오는 번호는 랜덤이다
  • 다음 과정을 순서대로 진행한다
    - cd / 명령어를 입력하여 루트 디렉토리로 이동한다
    - ls 명령어를 입력하여 루트 디렉토리에 data 폴더가 존재하는지 확인한다
root@538de4e5e997:/usr/local/apache2# cd /

root@538de4e5e997:/# ls

bin   data  etc   lib	 mnt  proc  run   srv  tmp  var  boot  dev   home  media  opt  root  sbin  sys  usr

 

      - cd data 명령어로 data 폴더로 이동한다
      - ls 명령어를 입력하여 quiz2.txt 파일이 존재하는지 확인한다

root@538de4e5e997:/# cd data

root@538de4e5e997:/data# ls

quiz2.txt

      - 명령어 apt update를 입력하여 apt 패키지 매니져를 업데이트 한다

root@538de4e5e997:/data# apt update


      - apt install nano 를 입력하여 nano 텍스트 에디터를 설치한다

root@538de4e5e997:/data# apt install nano

 

  • quiz2.txt 명령어를 입력해 quiz2.txt 파일 안에 적힌 단어를 확인한다
    - nano 창으로 이동하여 단어가 출력된다
nano quiz2.txt

  • 컨테이너 터미널은 exit명령어를 통해 종료할 수 있습니다.
 

3. 두 개의 Docker Image를 다루는 방식

  • nginx 이미지를 기반으로 한 sebcontents/client 이미지를 이용하여 client 컨테이너를 생성한다
  • spring 이미지를 기반으로 한 0xnsky/server-spring 이미지를 이용하여 server 컨테이너를 생성한다
  • localhost 의 8080 번 포트로 접속하면, sebcontents/client 이미지를 이용해 배포한 client 컨테이너의 80 번 포트로 연결한다

 1) docker-compose CLI

docker compose

 

Overview of docker compose CLI

 

docs.docker.com

 

  • docker-compose up # -d 명령어는 docker-compose.yaml에 정의된 이미지를 컨테이너로 실행한다
docker-compose up # -d

docker-compose down 명령어는 docker-compose.yaml에 정의된 이미지를 이용해 실행된 컨테이너를 종료한다

docker-compose down

docker-compose up 명령어는 특정 이미지만 컨테이너로 실행한다

docker-compose up {특정 이미지}

 

 2) 두 개 이상의 도커 컨테이너를 연결하는 docker-compose

  • docker-compose.yaml 혹은 docker-compose.yml 파일을 아래 소스 코드를 그대로 붙여넣어 생성한다
    - 파일을 생성할 때 터미널의 위치는 무관하다
    - 에디터를 사용해도 무관하다
version: '3.8'

services:
  nginx:
    image: sebcontents/client
    restart: 'always'
    ports:
      - "8080:80"
    container_name: client

  spring:
    image: 0xnsky/server-spring
    restart: 'always'
    ports:
      - "4999:3000"
    container_name: server-spring

  • yaml 파일이 있는 위치에서 docker-compose up -d명령어를 통해 yaml 파일을 실행한다
    - 새로운 서버가 도크에 생성된다
    - 두개의 컨테이너가 연결되어 있는것을 확인할 수 있다

  • 브라우저에서 localhost:8080 혹은 127.0.0.1:8080 에서 실행한다

 


3. Docker Volume

 

볼륨 (컴퓨팅) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 컴퓨터 운영 체제 환경에서, 볼륨(volume) 또는 논리 드라이브(logical drive)는 하나의 파일 시스템을 갖춘 하나의 접근 가능한 스토리지 영역으로, 일반적으로(꼭

ko.wikipedia.org

 

도커 컨테이너 데이터 볼륨 관리

도커는 하나의 이미지로 부터 여러 컨테이너를 만들기 위해서 Union File system을 사용한다. 유니온 파일 시스템은 원본 이미지에 변경된 내용(diff)를 추가하는 방식이므로, 다른 컨테이너에서 사용

www.joinc.co.kr

 

  1. docker-compose.yaml 혹은 docker-compose.yml 파일을 다음의 내용을 그대로 붙여넣어 생성한다
    - 파일을 생성할 때 터미널의 위치는 무관하다
    - M1 칩을 사용하는 노트북은 사용하는 mysql 이미지가 다르다
    - 자신의 노트북 환경을 확인한 후 알맞은 yaml 파일을 생성한다
  • M1을 제외한 다른 노트북 모델용 yaml 파일
        version: '3.8'
        
        services:
          nginx:
            image: sebcontents/client
            restart: 'always'
            ports:
              - "8080:80"
            container_name: client
        
          spring:
            image: 0xnsky/server-spring
            restart: 'always'
            ports:
              - "4999:3000"
            container_name: server-spring
            volumes:
              - "./volumefolder:/data"
        
          mysql:
            image: mysql:latest
            restart: 'always'
            ports:
              - "3307:3306"
            container_name: database
            environment:
              MYSQL_ROOT_PASSWORD: root_계정_비밀번호
              MYSQL_DATABASE: 초기_생성_데이터베이스
              MYSQL_USER: 유저_이름
              MYSQL_PASSWORD: 유저_패스워드
        

 

  • M1 노트북용 yaml 파일
        version: '3.8'
        
        services:
          nginx:
            image: sebcontents/client
            restart: 'always'
            ports:
              - "8080:80"
            container_name: client
        
          spring:
            image: 0xnsky/server-spring
            restart: 'always'
            ports:
              - "4999:3000"
            container_name: server-spring
            volumes:
              - "./volumefolder:/data"
        
          mysql:
            image: amd64/mysql
            restart: 'always'
            ports:
              - "3307:3306"
            container_name: database
            environment:
              MYSQL_ROOT_PASSWORD: root_계정_비밀번호
              MYSQL_DATABASE: 초기_생성_데이터베이스
              MYSQL_USER: 유저_이름
              MYSQL_PASSWORD: 유저_패스워드
        
 
 

Mysql - Official Image | Docker Hub

We and third parties use cookies or similar technologies ("Cookies") as described below to collect and process personal data, such as your IP address or browser information. You can learn more about how this site uses Cookies by reading our privacy policy

hub.docker.com

 

  • docker-compose를 통해 컨테이너가 제대로 실행되었다면 로컬의 yaml 파일이 위치한 곳에 volumefolder 라는 디렉토리를 확인할 수 있다
    - volumefolder 디렉토리 속에 임의의 텍스트 파일 하나를 생성한다
  1. docker exec -it server-spring bash 명령어를 통해 server 컨테이너 터미널로 접속한다
    - 컨테이너 터미널에서 cd /data 명령어를 입력해 data 디렉토리로 이동한다
  2. server-spring 컨테이너의 /data위치에 2번 과정에서 여러분이 로컬에서 생성했던 임의의 텍스트 파일이 존재하는지 확인한다

Precaution

  • Ubuntu 운영체제로 실습을 진행하는 경우, 관리자 권한(sudo)으로 Docker 명령어를 실행해야 한다
  • 진행 중에 permission denied 메시지가 포함된 오류가 발생하면 sudo 를 명령어 앞에 붙여서 관리자 권한을 부여한다

 

1. Docker CLI(Command Line Interface)

 

docker container run

docker container run: Run a command in a new container

docs.docker.com

도커를 이용하는 데 있어서 명령어, 옵션 등 사용법은 Docker docs에서 확인할 수 있다

Docker CLI 관련 정보뿐만 아니라 Docker의 전반적인 사용법과 환경을 구성하는 방법에 대해서도 확인할 수 있다
- 사용법 : Docker CLI, Docker-Compose CLI, API Reference
- 환경 및 빌드 파일 구성 : DockerFile, Docker-Compose File

 

 

 

2. Docker 명령

 1) 실행 명령

docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
  • Option
Option Default Description
--add-host   사용자 지정 호스트-IP 매핑 추가(host:ip)
--attach,-a   STDIN, STDOUT 또는 STDERR에 연결
--blkio-weight   차단 IO(상대 가중치), 10~1000, 비활성화하려면 0(기본값 0)
--blkio-weight-device   블록 IO 가중치(상대 장치 가중치)
--cap-add   Linux 기능 추가
--cap-drop   Linux 기능 중단
--cgroup-parent   컨테이너에 대한 선택적 상위 cgroup
--cgroupns   API 1.41+
사용할 Cgroup 네임스페이스(host|private) 'host': Docker 호스트의 cgroup 네임스페이스에서 컨테이너를 실행 'private': 자체 private cgroup 네임스페이스에서 컨테이너를 실행합니다. '': default-cgroupns-에 의해 구성된 대로 cgroup 네임스페이스를 사용합니다. 데몬의 모드 옵션(기본값)
--cidfile   파일에 컨테이너 ID 쓰기
--cpu-count   CPU 수(Windows만 해당)
--cpu-percent   CPU 백분율(Windows만 해당)
--cpu-period   CPU CFS(Completely Fair Scheduler) 기간 제한
--cpu-quota   CPU CFS(Completely Fair Scheduler) 할당량 제한
--cpu-rt-period   마이크로초 단위의 CPU 실시간 기간 제한
--cpu-rt-runtime   마이크로초 단위로 CPU 실시간 런타임 제한
--cpu-shares,-c   CPU 공유(상대 가중치)
--cpus   CPU 수
--cpuset-cpus   실행을 허용할 CPU(0-3, 0,1)
--cpuset-mems   실행을 허용할 MEM(0-3, 0,1)
--detach,-d   백그라운드에서 컨테이너 실행 및 컨테이너 ID 인쇄
--detach-keys   컨테이너 분리를 위한 키 시퀀스 재정의
--device   컨테이너에 호스트 장치 추가
--device-cgroup-rule   cgroup 허용 장치 목록에 규칙 추가
--device-read-bps   장치의 읽기 속도(초당 바이트 수) 제한
--device-read-iops   장치의 읽기 속도(초당 IO) 제한
--device-write-bps   장치에 대한 쓰기 속도(초당 바이트 수) 제한
--device-write-iops   장치에 대한 쓰기 속도(초당 IO) 제한
--disable-content-trust true 이미지 확인 건너뛰기
--dns   사용자 지정 DNS 서버 설정
--dns-opt   DNS 옵션 설정
--dns-option   DNS 옵션 설정
--dns-search   사용자 지정 DNS 검색 도메인 설정
--domainname   컨테이너 NIS 도메인 이름
--entrypoint   이미지의 기본 ENTRYPOINT를 덮어씁니다.
--env,-e   환경 변수 설정
--env-file   환경 변수 파일에서 읽기
--expose   포트 또는 포트 범위 노출
--gpus   API 1.40+
컨테이너에 추가할 GPU 장치(모든 GPU를 전달하려면 'all')
--group-add   가입할 그룹 추가
--health-cmd   상태를 확인하기 위해 실행하는 명령
--health-interval   검사 실행 사이의 시간(ms|s|m|h)(기본값 0)
--health-retries   비정상 보고에 필요한 연속 실패
--health-start-period   상태 재시도 카운트다운을 시작하기 전에 컨테이너가 초기화되는 시작 기간(ms|s|m|h)(기본값 0)
--health-timeout   하나의 검사를 실행할 수 있는 최대 시간(ms|s|m|h)(기본값 0s)
--help   인쇄 사용
--hostname,-h   컨테이너 호스트 이름
--init   신호를 전달하고 프로세스를 거두는 컨테이너 내부에서 init 실행
--interactive,-i   연결되지 않은 경우에도 STDIN을 열어 두십시오.
--io-maxbandwidth   시스템 드라이브의 최대 IO 대역폭 제한(Windows만 해당)
--io-maxiops   시스템 드라이브의 최대 IOPS 제한(Windows만 해당)
--ip   IPv4 주소(예: 172.30.100.104)
--ip6   IPv6 주소(예: 2001:db8::33)
--ipc   사용할 IPC 모드
--isolation   컨테이너 격리 기술
--kernel-memory   커널 메모리 제한
--label,-l   컨테이너에 메타 데이터 설정
--label-file   줄로 구분된 레이블 파일 읽기
--link   다른 컨테이너에 링크 추가
--link-local-ip   컨테이너 IPv4/IPv6 링크 로컬 주소
--log-driver   컨테이너용 로깅 드라이버
--log-opt   로그 드라이버 옵션
--mac-address   컨테이너 MAC 주소(예: 92:d0:c6:0a:29:33)
--memory,-m   메모리 제한
--memory-reservation   메모리 소프트 제한
--memory-swap   메모리 + 스왑과 동일한 스왑 제한: 무제한 스왑을 활성화하려면 '-1'
--memory-swappiness -1 컨테이너 메모리 스와핑 조정(0~100)
--mount   파일 시스템 마운트를 컨테이너에 연결
--name   컨테이너에 이름 할당
--net   컨테이너를 네트워크에 연결
--net-alias   컨테이너에 대한 네트워크 범위 별칭 추가
--network   컨테이너를 네트워크에 연결
--network-alias   컨테이너에 대한 네트워크 범위 별칭 추가
--no-healthcheck   컨테이너 지정 HEALTHCHECK 비활성화
--oom-kill-disable   OOM 킬러 비활성화
--oom-score-adj   호스트의 OOM 기본 설정 조정(-1000 ~ 1000)
--pid   사용할 PID 네임스페이스
--pids-limit   컨테이너 pid 제한 조정(무제한의 경우 -1로 설정)
--platform   서버가 다중 플랫폼을 지원하는 경우 플랫폼 설정
--privileged   이 컨테이너에 확장 권한 부여
--publish,-p   컨테이너의 포트를 호스트에 게시
--publish-all,-P   노출된 모든 포트를 임의의 포트에 게시
--pull missing 실행하기 전에 이미지 가져오기("항상"|"누락"|"절대")
--read-only   컨테이너의 루트 파일 시스템을 읽기 전용으로 마운트
--restart no 컨테이너 종료 시 적용할 재시작 정책
--rm   컨테이너가 종료되면 자동으로 제거
--runtime   이 컨테이너에 사용할 런타임
--security-opt   보안 옵션
--shm-size   /dev/shm의 크기
--sig-proxy true 프록시가 프로세스에 신호를 수신했습니다.
--stop-signal SIGTERM 컨테이너 중지 신호
--stop-timeout   컨테이너 중지 시간 초과(초)
--storage-opt   컨테이너에 대한 스토리지 드라이버 옵션
--sysctl   시스템 옵션
--tmpfs   tmpfs 디렉토리 마운트
--tty,-t   의사 TTY 할당
--ulimit   Ulimit 옵션
--user,-u   사용자 이름 또는 UID(형식: <name|uid>[:<group|gid>])
--userns   사용할 사용자 네임스페이스
--uts   사용할 UTS 네임스페이스
--volume,-v   볼륨 마운트 바인딩
--volume-driver   컨테이너용 옵션 볼륨 드라이버
--volumes-from   지정된 컨테이너에서 볼륨 마운트
--workdir,-w   컨테이너 내부의 작업 디렉토리
  • Parent Command
docker container Manage containers
  • Related Command
Command 단독 사용 시
docker container COMMAND
Command Description
docker container attach 실행 중인 컨테이너에 로컬 표준 입력, 출력 및 오류 스트림 연결
docker container commit 컨테이너의 변경 사항에서 새 이미지 만들기
docker container cp 컨테이너와 로컬 파일 시스템 간에 파일/폴더 복사
docker container create 새 컨테이너 만들기
docker container diff 컨테이너의 파일 시스템에서 파일 또는 디렉토리의 변경 사항 검사
docker container exec 실행 중인 컨테이너에서 명령 실행
docker container export 컨테이너의 파일 시스템을 tar 아카이브로 내보내기
docker container inspect 하나 이상의 컨테이너에 대한 자세한 정보 표시
docker container kill 하나 이상의 실행 중인 컨테이너 종료
docker container logs 컨테이너의 로그 가져오기
docker container ls 컨테이너 나열
docker container pause 하나 이상의 컨테이너 내 모든 프로세스 일시 중지
docker container port 컨테이너에 대한 포트 매핑 또는 특정 매핑 나열
docker container prune 중지된 모든 컨테이너 제거
docker container rename 컨테이너 이름 바꾸기
docker container restart 하나 이상의 컨테이너 다시 시작
docker container rm 하나 이상의 컨테이너 제거
docker container run 새 컨테이너에서 명령 실행
docker container start 하나 이상의 중지된 컨테이너 시작
docker container stats 컨테이너 리소스 사용 통계의 라이브 스트림 표시
docker container stop 하나 이상의 실행 중인 컨테이너 중지
docker container top 컨테이너의 실행 중인 프로세스 표시
docker container unpause 하나 이상의 컨테이너 내의 모든 프로세스 일시 중지 해제
docker container update 하나 이상의 컨테이너 구성 업데이트
docker container wait 하나 이상의 컨테이너가 중지될 때까지 차단한 다음 종료 코드를 인쇄합니다.

 

2. Docker 실습 - 이미지 실행

 1) docker/whalesay

  • docker/whalesay 라는 이미지로 실습한다
  • 제공된 이미지 docker/whalesay는 레지스트리 계정, 레포지토리 이름, 태그 세 가지 정보로 구성되어 있다

  • 레지스트리(Registry)
    - Docker Hub : https://hub.docker.com/
    - 도커 이미지를 관리하는 공간이다
    - 별도로 지정하지 않으면 도커 허브(Docker Hub)를 기본 레지스트리로 설정한다
    - 레지스트리는 Docker Hub, Private Docker Hub, 회사 내부용 레지스트리 등으로 나눌 수 있다
  • 레포지토리(Repository)
    - 레지스트리 내에 도커 이미지가 저장되는 공간이다
    - 이미지 이름이 사용되기도 한다
    - GitHub의 레포지토리와 유사하다
  • 태그(Tag)
    - 동일한 이미지도 버전 별로 안의 내용이 다를 수 있다
    - 해당 이미지를 설명하는 버전 정보를 입력한다
    - 별도로 지정하지 않으면 latest 태그를 붙인 이미지를 가져온다
  • docker/whalesay:latest 이미지 정보를 해석하면
    - Docker Hub 레지스트리에서
    - docker라는 유저가 등록한 whalesay 이미지 혹은 레포지토리에서
    - latest 태그를 가진 이미지이다
  • 이미지 : https://hub.docker.com/r/docker/whalesay
 

Docker Hub

 

hub.docker.com

 

Docker Hub Container Image Library | App Containerization

We and third parties use cookies or similar technologies ("Cookies") as described below to collect and process personal data, such as your IP address or browser information. You can learn more about how this site uses Cookies by reading our privacy policy

hub.docker.com

 

 

 2) Docker 레포지토리 생성

  • Docker에 가입한다
  • Repository를 생성한다

  • 정상적으로 레포지토리가 생성되었다

 

 

 

3) 이미지 실습 : docker/whalesay

docker image pull docker/whalesay:latest

  • 도커에 있는 이미지 리스트를 출력한다
docker image ls

  • 이미지를 실행한다
    - {container} run
      : 컨테이너를 실행한다
    - [OPTIONS]
      : -name : 컨테이너의 이름을 할당한다
    - [COMMAND]
      : command는 초기 컨테이너 실행 시 수행되는 명령어이다
      : cowsay : 컨테이너 실행 시 cowsay 명령어를 호출하며, node를 호출하듯 이용한다
    - [ARG..]
      : boo : COMMAND인 cowsay에 넘겨질 파라미터이다
docker container run 사용법
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]

예시
docker container run --name 컨테이너_이름

docker/whalesay 실행 명령
docker container run docker/whalesay:latest cowsay boo

 

 3-1) 대표 명령문

  • 도커에 있는 컨테이너 리스트를 출력한다
    - a : Default 로는 실행되는 컨테이너지만 종료된 컨테이너를 포함하여 모든 컨테이너를 출력한다
docker container ps -a

  • 도커에 있는 컨테이너를 삭제한다 
    - {container} rm : 컨테이너를 지정하여 삭제한다
    - 컨테이너를 지정 삭제할 경우에는 ps 명령으로 NAMES 또는 CONTAINER ID 를 확인한 후 사용한다
docker container rm 컨테이너_이름
  • 지정된 도커의 이미지를 삭제한다
docker image rm docker/whalesay

 

  • 연속 작업을 실행한다
    - {container} run : 컨테이너를 실행한다
    - 이미지가 없다면 이미지를 받아온 뒤(pull) 실행한다
    - -rm : 컨테이너를 일회성으로 실행한다
      : 컨테이너가 중지되거나 종료될 때, 컨테이너와 관련된 리소스를 모두 제거한다
docker container run --name 컨테이너_이름 --rm docker/whalesay cowsay boo

 

 

 4) 파일 실습 : danielkraic/asciiquarium

  • danielkraic라는 사람이 올린 이미지 asciiquarium 를 실행한다
docker container run -it --rm danielkraic/asciiquarium:latest
  • [커맨드] danielkraic/asciiquarium 이미지를 실행합니다.
  • it : -i, -t 를 동시에 사용한 옵션이다
    - 사용자와 컨테이너 간에 인터렉션(interaction)이 필요할 경우 사용한다
    - 출력되는 화면을 사용자가 지속적으로 보기 위해 사용하였다
    - Python 명령이 필요하거나 추가로 다른 입력을 받는다면, 이 옵션을 지정한 뒤 사용한다
  • container는 ctrl + c 로 종료할 수 있다

 

 

3. Docker 컨테이너에 파일 복사

  • 게임 서버, 웹 서버와 같이 사용할 도구가 도커 이미지에 모두 구성되어 있지 않은 경우도 있다
    - 웹 서버는 도커 컨테이너로 실행하고,  웹 서버를 구성하는 파일은 직접 만들거나 가져온 파일로 구성될 수 있다
    - 장점
     : 서버에 발생한 문제를 호스트와 별개로 파악할 수 있다
     : 문제가 생긴 서버를 끄고, 공장 초기화를 하듯 도커 이미지로 서버를 재구동할 수 있다
  • 로컬에 있는 파일과 도커 이미지를 연결하는 방법은 크게 2가지로 나눈다
    - CP(Copy) : 호스트와 컨테이너 사이에 파일을 복사(Copy)하는 방법
    - Volume : 호스트와 컨테이너 사이에 공간을 마운트(Mount)하는 방법
      : 마운트는 저장 공간을 다른 장치에서 접근할 수 있도록 경로를 허용하여 하나의 저장 공간을 이용하는 것처럼 보이게 하는 작업이다

 1) httpd 웹 서버

  • 사용할 도커 이미지는 httpd(http daemon)이다
    - httpd(http daemon)은 Apache HTTP Server를 실행할 수 있는 오픈소스 웹 서버 소프트웨어이다
    - https://httpd.apache.org/ 
 

Welcome! - The Apache HTTP Server Project

The Number One HTTP Server On The Internet The Apache HTTP Server Project is an effort to develop and maintain an open-source HTTP server for modern operating systems including UNIX and Windows. The goal of this project is to provide a secure, efficient an

httpd.apache.org

  • httpd 는 /usr/local/apache2/htdocs/ 경로에 웹 서버와 관련된 파일들이 저장되어 있다면, 해당 파일을 기반으로 웹 서버가 실행되도록 한다
  • codestates-seb/be-pacman-canvas 레포지토리를 클론한다
    - [커맨드] be-pacman-canvas 레포지토리를 클론한다
git clone git@github.com:codestates-seb/be-pacman-canvas.git

또는 아래 파일을 클론한다

git clone https://github.com/codestates-seb/be-pacman-canvas.git

  • docker container run 명령어로 httpd 를 실행합니다.[커맨드] httpd를 실행한다 
    - -name : httpdserver 을 입력하여 아파치 서버로 지정한다
    - -p : 로컬호스트의 포트와 컨테이너의 포트를 연결한다
    - 818포트가 로컬호스트의 포트이고, 80번은 컨테이너의 포트이다
    - httpd 는 일정 시간 연결 기록이 없으면, 서버 가동이 중지된다
    - 실행 중이던 도커 컨테이너가 중지되면 다시 실행한다
    - 터미널에서 명령어를 입력했을 때, 아래 화면처럼 터미널의 작동이 중단된 것처럼 보여도 정상적인 상태이다
      : 터미널을 종료하지 말고, 다른 터미널 창을 열어 다른 작업을 수행한다
docker container run --name 컨테이너_이름 -p 818:80 httpd

docker container run --name httpdserver -p 818:80 httpd

  • -d 는 컨테이너를 백그라운드에서 실행하게 해주는 옵션이다
  • 127.0.0.1:818 또는 localhost:818 을 통해 웹 서버가 작동하고 있는지 확인한다
    - 127.0.0.1 과 localhost 를 이용하면 로컬 컴퓨터의 IP 주소로 redirecting 할 수 있다
    - DNS 설정으로 인해 localhost 로 접속이 안 되고 127.0.0.1 만 접속이 될 수 있다

 

  • 서버가 정상적으로 열린 것이 확인되면 새로운 터미널을 연다
  • docker container cp 명령어를 입력해 로컬호스트에 있는 파일을 컨테이너에 전달한다
    - 경로를 입력할 때, 상대 경로와 절대 경로를 주의한다
    - docker container cp 명령은 앞에 있는 경로의 파일을 뒤에 있는 경로에 복사한다
지정된 경로에서 명령어를 입력한다

//src/main/resource/template
docker container cp ./ 컨테이너_이름:/usr/local/apache2/htdocs/

//src/main/resource/static
docker container cp ./ 컨테이너_이름:/usr/local/apache2/htdocs/

각각의 경로 위치에서 실행한다
  • 127.0.0.1:818 혹은 localhost:818 에 접속해서 게임 서버가 구동되는지 확인합니다.

  • 컨테이너를 다룰 때, 뭔가 제대로 되지 않는다면
    - docker exec -it 컨테이너_이름 bash 명령어를 통해 컨테이너 내부 터미널로 접속할 수 있다
    - 팩맨 실습에서는 컨테이너 속 '/usr/local/apache2/htdocs' 경로에 주의한다

 

 

4. Docker 이미지 만들기

  • Docker Container를 이미지 파일로 변환한다
  • 이미지로 변환하면
    - 이전에 작업했던 내용을 다시 한 번 수행하지 않아도 된다
    - 배포 및 관리가 유용하다

 1) 구동한 Docker Container를 이미지로 만드는 방법

docker container commit 컨테이너_이름 my_pacman:1.0

 

  •  docker image ls 명령어로 이미지가 만들어진 것을 확인한다

 

  • 생성된 이미지를 900 포트에서 웹 서버로 구동한다
    - [커맨드] 900 포트에서 웹 서버로 이미지를 구동한다
docker run --name my_web2 -p 900:80 my_pacman:1.0
  • 127.0.0.1:900 또는 localhost:900 을 통해 웹 서버가 작동하고 있는지 확인한다

 

2) Docker Image 빌드를 위한 파일인 Dockerfile 로 만드는 방법

https://docs.docker.com/engine/reference/builder/ 

 

Dockerfile reference

 

docs.docker.com

 

  • Dockerfile 을 만들고, Dockerfile 대로 이미지를 build 하는 방법이다
  • Dockerfile 은 이미지 파일의 설명서다
  • Dockerfile로 pacman 이미지를 생성한다
    - COPY 구문에서 Dockerfile의 생성 위치를 확인한다
베이스 이미지를 httpd:2.4 로 사용합니다.
FROM httpd:2.4

호스트의 현재 경로에 있는 파일을 생성할 이미지 /usr/local/apache2/htdocs/ 에 복사한다
COPY ./ /usr/local/apache2/htdocs/

 

  • docker build 명령어는 Dockerfile로 도커 이미지 파일을 생성할 때 사용한다
--tag 는 name:tag 형식으로 이미지를 생성할 수 있다
지정한 경로에 있는 Dockerfile을 찾아서 빌드한다

docker build --tag my_pacman:2.0 .

"."을 명령어에 꼭 포함해야 한다

 

  • 생성된 이미지를 이용해 901 포트에 웹 서버를 구동한다
docker run --name my_web3 -p 901:80 my_pacman:2.0
  • 127.0.0.1:901 또는 localhost:901 을 통해 웹 서버가 작동하고 있는지 확인한다
    - Docker Desktop 에 my_web3 이름으로 port 901 이 생성됭 것을 확인할 수 있다

 

 

Docker Desktop의 기본 수행은 필히 진행한다

 

1. Docker 설치

 

이전 버전 WSL의 수동 설치 단계

wsl install 명령을 사용하지 않고 이전 버전의 Windows에 WSL을 수동으로 설치하는 방법에 대한 단계별 지침입니다.

docs.microsoft.com

 

Install Docker Desktop on Windows

 

docs.docker.com

  • Close ans log out 를 클릭한다(재부팅 되니까 다른 프로그램 사용 시 주의)

  • 설치 후 터미널에서 Docker 설치 여부 및 버젼을 확인 한다 

 

2. 컨테이너

 1) 일반 컨테이너의 장점

  • 물자를 싣고 내릴 때에, 선박이 입항해 있는 시간을 단축할 수 있다
  • 물자를 싣고 내릴 때 필요한 인력(분류하는 사람, 짐 옮기는 사람, 감독하는 사람)을 감소할 수 있다

 

 2) 리눅스 컨테이너(Linux Container, lxc)

  • 컨테이너 기술을 소프트웨어 배포에 사용하기 위하여 적용한 시스템이다
  • 애플리케이션이 의존성, 네트워크 환경, 파일 시스템에 구애받지 않고, 도커라는 기술 위에 실행될 수 있도록 만든 애플리케이션 상자이다
  • 애플리케이션을 쉽게 컨테이너화할 수 있는 생태계 혹은 커뮤니티가 없었다
  • 2013년에 등장한 도커(Docker)는 Docker Hub라는 소프트웨어 저장소와 함께 빠르게 성장했다
  • 개발자들은 쉽게 애플리케이션을 포장하고, 컨테이너 방식으로 실행할 수 있게 되었다

 3) 컨테이너의 격리 요소

  • 프로세스
    - 특정 컨테이너에서 작동하는 프로세스는 기본적으로 그 컨테이너 안에서만 액세스할 수 있다
    - 컨테이너 안에서 실행되는 프로세스는 다른 컨테이너의 프로세스에게 영향을 줄 수 없다
  • 네트워크
    - 기본으로 컨테이너 하나에 하나의 IP 주소가 할당된다
  • 파일 시스템
    - 컨테이너 안에서 사용되는 파일 시스템은 구획화되어 있다
    - 해당 컨테이너에서의 명령이나 파일 등의 액세스를 제한할 수 있다

 

 

3. 컨테이너의 장점

 1) 개발 및 배포 환경의 일치

  • 애플리케이션 실행은 환경에 구애를 받는다
    - 프로그램(A) 실행에 프로그램(B)가 반드시 필요한 경우, '프로그램 A는 프로그램 B에 의존 관계를 가진다'고 한다
  • 한 개의 컴퓨터에서 여러 개의 프로그램을 실행할 경우 의존성이 각각 다르면 충돌이 발생한다

  • 컨테이너의 적용으로 실행 환경에 구애받지 않고 애플리케이션을 실행할 수 있다

 

 

  • OS에 상관없이 애플리케이션 실행 환경을 만들 수 있다
  • 개발을 컨테이너 위에서 진행할 경우 동일한 환경 하에 개발을 진행할 수 있다

 

  2) 편리한 수평 확장 및 배포의 편이성

  • 로드밸런서(Load Balancer)
    - 리버스 프록시 서버(Revers Proxy server)의 한 종류이다
    - 다수의 사용자가 동일한 서비스를 이용할 경우 많은 양의 트래픽이 발생한다
    - 서비스 제공자들은 이러한 트래픽 분산을 위해 프록시 서버를 운영한다
    - 프록시 서버는 여러 대의 동일한 검색 서버 중 여유로운 서버를 이용할 수 있도록 도와준다

  • 동일한 애플리케이션 구성(이미지)을 바탕으로 새로운 서버에 해당 애플리케이션을 컨테이너로 실행하고, 로드 밸런서에 이 서버를 추가하기만 하면 된다
    - AWS의 경우 서버를 만들고 삭제하는 일을 자동으로 해 준다

  • 새로운 버전의 애플리케이션을 여러 서버 중 몇 대에만 운영하여 테스트 할 수도 있다
    - 새로운 버전의 애플리케이션에서 발생할 수 있는 문제들을 미리 확인할 수 있다
  • 쿠버네티스와 같이 "오케스트레이션 도구"라고 부르는 것들이 이러한 일을 해주는 도구이다
    - https://www.redhat.com/ko/topics/containers/what-is-container-orchestration
 

컨테이너 오케스트레이션이란?

컨테이너 오케스트레이션은 컨테이너의 배포, 관리, 확장, 네트워킹을 자동화합니다.

www.redhat.com

 

4. 이미지(Image)

  • 실행되는 모든 컨테이너는 이미지로부터 생성된다
    - 이미지는 애플리케이션 및 애플리케이션 구성을 함께 담아놓은 템플릿이다
    - 이미지를 이용해서 컨테이너를 만들 수 있다
  • 이미지를 이용해 여러 개의 컨테이너를 생성할 수 있다
    - 애플리케이션의 수평 확장이 가능하다
  • 이미지는 기본 이미지(base image)로부터 다른 이미지를 만들 수도 있다
    - git을 사용하는 것처럼 변경 사항을 추가하고 커밋하여 이미지를 변경할 수 있다
  • 스프링부트 기반의 애플리케이션을 이미지로 만들고 싶은 경우
    - 스프링부트 초기 세팅 이미지를 기본 이미지로 하고 제작한 애플리케이션을 추가하여 이미지화할 수 있다
  • 이미지는 레지스트리에 저장된다
  • 대표적인 이미지 레지스트리에는 Docker Hub, Amazon ECR 이 있다
  • 도커 CLI에서 이미지를 이용해 컨테이너를 생성할 경우
    - 호스트 컴퓨터에 이미지가 존재하지 않으면 기본 레지스트리로부터 다운로드 받는다

 

 

1. Create & Access RDS Instance

  • MySQL 데이터베이스 엔진을 사용하는 DB 인스턴스를 생성한다
  • 로컬 환경에서 MySQL 클라이언트를 활용하여 DB 인스턴스에 연결한다

 

 1) RDS 인스턴스 생성

  • AWS 메인 콘솔 창에서 RDS를 검색하여 RDS 메인 화면으로 이동한다

 

  • 사이드 바에 위치한 '데이터베이스' 메뉴를 클릭하여 이동한다

  • 데이터베이스 메뉴로 이동한 뒤, 화면에 보이는 '데이터베이스 생성' 버튼을 클릭합니다.

  • DB 인스턴스의 생성과 관련된 여러 가지 옵션을 지정할 수 있는 페이지가 나온다
  • 데이터베이스 엔진 옵션 중에 MySQL을 사용하여 실습을 진행할 것이기에 MySQL 을 선택한다
  • 스크롤을 아래로 내려서 템플릿을 프리 티어 옵션으로 선택한다

 

  • 아래로 더 이동하여 설정 옵션 창에서 'DB 인스턴스 식별자'의 이름과 '자격증명 설정'의 마스터 사용자 이름, 마스터 암호를 기재한다
    - 마스터 사용자 이름과 암호는 나중에 데이터베이스를 연결할 때 쓰이는 정보로 반드시 기억한다

 

  • 아래로 이동해서 RDS 인스턴스 구성의 DB 인스턴스 클래스를 버스터블 클래스, db.t2.micro로 선택한다

 

  • 아래로 한참 내려와서 연결로 이동 후 '퍼블릭 액세스'를 '예'로 설정한다

  • VPC 보안 그룹 설정은 기본 항목 선택, 기존 VPC보안 그룹은 default를 설정한다
    - 다른 보안 그룹을 선택 시 로컬 환경 터미널에서 테스트가 불가능하다
  • 추가 구성 토글을 열면 '데이터베이스 포트' 항목이 열리고 번호를 지정할 수 있는 창이 나타난다
    - 일반적으로 사용되는 3306번 포트 대신, 포트 번호 노출을 방지하려는 목적으로 13306번으로 지정한다
 
  • 아래로 이동하여 '추가 구성' 토글을 클릭한다
  • 추가 구성 토글을 열면 여러 옵션을 추가로 확인 가능하다
    - 초기 데이터베이스 이름은 'test'로 설정한다
    - 서버 코드가 'test' 데이터베이스를 찾도록 작성되어 있으니, 다른 이름은 사용하지 않는다
  • 초기 데이터베이스 이름을 기입하고 나면 모든 설정이 끝난다

  • '데이터베이스 생성' 버튼을 클릭하여 DB 인스턴스를 생성한다

  • DB 인스턴스가 생성되기까지 시간이 오래 걸리므로 '상태' 부분이 현재 '생성 중'으로 보인다면 잠시 대기한다
    - '사용 가능'으로 변화하면 DB 인스턴스 생성이 완료된다

 

 

 2) MySQL 클라이언트를 활용 DB 인스턴스 연결

  • MySQL 클라이언트를 통해 RDS의 DB 인스턴스에 연결하기 위해서는 세 가지 정보가 필요하다
    1. DB 인스턴스 생성 시 기재한 마스터 이름, 마스터 비밀번호
    2. 포트 번호
    3. 생성한 DB 인스턴스의 엔드 포인트 주소
  • 포트 번호와 마스터 유저 정보 부분은 생성 과정에서 교육처에서 기재했기 때문에 넘어간다
  • 엔드 포인트 주소를 확인하기 위해서 생성한 DB 인스턴스를 클릭한다
  • 연결 & 보안 메뉴 부분을 보시면 엔드 포인트 주소를 확인할 수 있다
    - 엔드 포인트 주소를 복사하여 저장한다

 
  • MySQL 을 통해서 DB 인스턴스에 접속한다
  • 터미널을 열고 'mysql -u [마스터 이름] --host [엔드 포인트 주소] -P 13306(포트 번호) -p' 명령어를 입력한다
    - 해당 명령어를 입력하면 비밀번호를 요구하는데, 마스터 비밀번호를 입력하면 된다
  • 명령어와 비밀번호를 바르게 작성했지만 접속이 되지 않는 경우 생성한 RDS 인스턴스의 보안그룹을 확인하여 인바운드 규칙에 접근하려는 IP(혹은 모든 곳에서 접근 허용)가 추가되어있는지 확인한다
    - 그래도 안되면 컴퓨터의 환경설정에 mysql>bin 주소를 등록되어 있는지 확인하고 없으면 등록해 준다
  • 정상적으로 진행이 된다면 아래와 같이 MySQL에 접속이 된다
  • 데이터베이스 연결 테스트를 하기 위해 터미널에 'show databases;' 를 입력한다

 
  • 아까 DB 인스턴스를 생성할 때 만들었던 초기 데이터베이스 'test'가 보인다면 정상적으로 연결이 된 것이다

 

 

2. 서버 환경 설정

  • EC2 인스턴스에서 실행되고 있는 서버는 작동은 하고 있지만, 데이터베이스와 클라이언트에 연결하지 않았다
  • 서버의 환경 설정을 통해 실습에서 생성한 RDS 인스턴스에 접속하고, 클라우드 데이터베이스를 사용할 수 있게 한다

 1) 서버 코드에 저장된 application.properties 파일에 환경 변수 설정하기

  • EC2 인스턴스에서 실행하고 있는 서버를 종료한다
    - java 를 이용해 프로세스로 실행 중인 경우 Command + C 로 종료한다
    - 백그라운드에서 실행중인 경우 실행중인 프로세스를 종료한다
  • 서버를 모두 종료한 후에 환경 설정 파일을 수정한다
    - application.properties 파일을 nano 에디터를 사용하여 수정한다
$ cd be-sprint-deployment/DeployServer/
$ nano src/main/resources/application.properties

  • application.properties 파일에서 아래의 환경변수를 변경한다
    - spring.datasource.url 변수에서 { }부분에 AWS RDS '엔드포인트 주소:포트' 를 넣어준다
    - spring.datasource.username 변수에는 AWS RDS Mysql 마스터 사용자 이름을 넣습니다.
    - spring.datasource.password 변수에는 AWS RDS Mysql 마스터 암호를 넣습니다.
    - config.domain 에는 AWS S3 Endpoint 주소를 넣습니다. 해당 부분에는 꼭 앞에 http:// 가 포함되어야 합니다.

  • 환경 변수를 변경이 끝나면 Ctrl + X 키를 입력하여 변경 내용을 저장한다
    - y 로 저장하고 enter로 빠져 나온다

  • application.properties 파일을 통한 환경 설정이 완료되면 EC2의 세션을 연결한다
  • 해당 폴더로 이동하여 이전 빌드를 삭제한다
$ ./gradlew clean

빌드를 모두 삭제

  • 새로 빌드를 진행한다
$ ./gradlew build

수정된 환경변수를 적용하여 다시 빌드를 진행

  • 서버를 다시 실행한다

 

3. 서버 실행

  • java -jar build/libs/DeployServer-0.0.1-SNAPSHOT.jar 명령어를 입력하여 서버를 재실행한다
  • Shell script를 이용해서 프로세스로 실행할 수도 있다
    - ./restart.sh명령어를 이용하여 서버를 다시 실행한다

  • 서버를 실행 후 s3 버킷의 엔드포인트 주소로 접속해서 연결 테스트를 진행한다
    - 이름에 김코딩, 비밀번호에 1234 를 입력한다

아래와 같은 메세지가 화면에 보인다면 데이터베이스 연결에 성공한 것입니다.

 

※ 참조

위 메세지가 보이지 않고 다른 메세지가 화면에 출력된다면 아래 사항을 체크해보세요.

  1. DB 인스턴스가 정상적으로 실행되고 있나요?
  2. 환경변수에 제대로 된 값을 할당했는지 확인합니다.
    • DB 인스턴스의 엔드포인트 값을 환경 변수에 넣을 때, 앞에 http://를 붙이면 안 되고, 포트 번호는 반드시 입력해야합니다. 위 스크린샷의 예시를 참고해주세요.
    • S3 주소를 입력할 때 다른 엔드포인트가 붙어있진 않은지 확인해주세요.
  3. RDS 엔드포인트를 사용해서 MySQL에 접속을 시도하지만, 타임오류로 접속이 되지 않아요!
  • AWS RDS 페이지에 접속한후, 사용중인 인스턴스를 클릭, 이후 연결/보안탭에서 VPN 보안그룹 정보를 클릭합니다. 
  • 우측 하단에 Edit inbound rules 버튼 클릭
  • 규칙 추가 클릭후, 유형에 모든 TCP, 소스에 모든 IP-V4를 클릭한후 해당 규칙을 추가한 이후에, 저장후 다시 확인하면 접속이 가능합니다.
  1. 크롬 개발자 도구의 Network 탭에 들어가서 요청, 응답 과정에서 어떤 오류가 발생하는지 확인합니다.

1. 클라이언트 배포를 위한 환경 설정

  • 실습에 적용한  repository의 클라이언트는 JavaScript, React를 이용해 작성되었다
  • 배포를 실습하기 전에 로컬 환경에서 Build 과정에 필요한 환경을 준비한다
  • EC2 인스턴스가 아닌 개인 PC, 로컬에서 진행해야 한다

 1) nvm 설치

https://github.com/nvm-sh/nvm#install--update-script

 

GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions

Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions - GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active nod...

github.com

  • 버전이 최신 버전으로 바뀌어 있을 수 있으니 확인한다

  • 아래 명령어를 터미널에 입력하여 wget 명령어로 nvm 설치를 시도한다
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash

  • Command 'wget' not found 메세지와 함께 설치가 진행되지 않는 경우에는 Package Manager를 이용해 wget을 설치한다
# Ubuntu
sudo apt update
sudo apt install wget

# macOS
brew update
brew install wget

 

2) nvm 설치 확인

  •  콘솔 화면에 'Close and reopen your terminal to start using nvm'이 출력되면 설치가 정상적으로 된 것이다
  • Close and reopen your terminal to start using nvm or run the following to use it now(터미널을 닫았다가 다시 열어 nvm 사용을 시작하거나 다음을 실행하여 지금 사용하십시오.)
    - 터미널을 닫았다가 다시 시작하는게 편한다

  • 터미널을 다시 시작한 후 다음 명령어를 이용하여 nvm 패키지 버전을 확인한다
nvm --version
  • 버전이 확인되면 NVM 설치가 성공한 것이다
    - nvm을 찾을 수 없다는 에러가 발생하여 다시 설치한 후 아래 명령어를 실행했다
    - 정상적으로 버전이 확인된다

 

 

 3) node.js 설치

  • nvm은 node.js의 다양한 버전을 쉽게 설치할 수 있다
  • nvm을 이용해 node.js를 설치한다
  • 아래 명령어와 같이 설치하려는 node 버전을 적어 준다
    - LTS(Long-Term Support)는 node.js에서 지원하는 기간이 길다는 의미이다
nvm install --lts
  • 설치된 node의 버전을 확인한다
node -v

 

2. S3 호스팅

 1) S3 버킷을 이용한 정적 웹 사이트 호스팅 과정

  • 정적 웹 사이트를 호스팅하는 과정은 4 단계로 요약된다
    - 첫 번째로 구현이 완성된 정적 웹 페이지를 빌드한다
    - 빌드 후 S3 대시보드에 접속하여 버킷을 생성한다
    - 생성한 버킷을 정적 웹 사이트 호스팅 용으로 구성한다
    - 빌드된 정적 웹 페이지를 버킷에 업로드한다
  • 업로드가 완료되면 퍼블릭 액세스 차단 설정을 해제하고, 다른 사용자의 접근 권한을 부여하는 버킷 정책을 생성한다
  • 모든 과정이 끝나면 사용자들이 버킷에 업로드된 정적 웹 페이지에 접속할 수 있다
 

 2) 정적 웹 페이지 빌드

  • 빌드(build)는 작성한 코드의 불필요한 데이터를 없애고, 통합 및 압축하여 배포에 적합한 상태를 만드는 과정이다
  • 빌드 과정을 통해 코드를 담고 있는 데이터의 용량은 줄어들고, 웹 사이트의 로딩 속도는 빨라진다
  • 클라이언트 프로젝트를 빌드하기 전에 터미널(WSL)을 이용하여 client 디렉토리로 이동한 후 클라이언트의 의존성 모듈을 설치한다
    - client 디렉토리는 실습용 프로젝트 코드를 clone한 폴더에서 진행한다
$ cd client
$ npm install
  • npm install 이 정상적으로 진행되지 않는다면 이전의 nvm, node 환경설정을 다시 확인해 본다

 

  2-1) 환경 변수 설정

  • 클라이언트의 경우 환경변수는 .env 파일에 선언한다
  • 실습 Repository에서  .env.example 파일을 찾아서 예시에 있는 주소를 입력한다
    - 요청을 보내는 서버의 주소를 환경 변수치에 담을 때는 필히 'http://' 나 'https://'를 포함해야 하며, 특정 포트에서 작동하는 경우 포트 번호까지 꼭 작성한다
    - 아래 이미지는 미리 .env로 변경한 다음 이미지이다
  • 최종적으로 이 파일의 이름은 .env.example이 아닌 .env여야 한다
    - 슬라이드의 명령어를 통해 파일의 이름을 변경할 수 있다

  • 해당 파일(.env 혹은 .env.example) client 디렉토리 내부에 위치하며, CLI에서 ls -a 혹은 ls -al 명령어로 확인할 수 있다
    - 파일을 수정할 수 있는 에디터(nano, vi 등)를 이용해 서버의 주소를 작성 후 저장한다
    - 파일 수정 명령어는 mv .env.example .env 이다

  •  환경 변수를 제대로 설정하지 않으면 서버에 요청을 제대로 보내지 못하게 되고, 그 결과로 정상적인 응답을 받아올 수 없다

 

   2-2) 빌드 진행

  •  터미널에 'npm run build' 명령어를 입력하여 빌드 과정을 진행한다
npm run build
  • 터미널 화면에 'Compiled Successfully'라는 문구가 보이며 빌드 과정이 마무리된다
  • 빌드 과정이 끝나면 client 디렉터리에 'build' 폴더가 생성된 것을 확인할 수 있다

  • 터미널에서 ls 명령어를 통해서도 확인할 수 있고, 파일탐색기(파인더) 혹은 IDE로도 확인할 수 있다
 

 

3) 버킷 생성

  • AWS 홈페이지에 로그인을 한다
  • 메인 콘솔 창에서 S3를 검색하여 S3 메인 화면에 접속한다

 
  • S3 메인 화면으로 이동하여 '버킷 만들기' 버튼을 클릭한다 

 

  • 버킷 만들기에서는 여러 옵션을 지정할 수 있다
  • '일반 구성' 옵션에 있는 내용만 완성한다
    - 버킷 이름을 작성한다
      : 버킷 이름은 각 리전에서 중복되지 않는 고유한 이름이어야 한다
      : 원하는 이름으로 버킷 이름을 작성한다
 
  • 버킷 이름 작성이 완료되면, 화면의 가장 아래에 있는 '버킷 만들기'라는 버튼을 클릭한다

  • 버킷이 성공적으로 생성되었다는 메시지와 아래 화면으로 이동된다

 3) 정적 구성

  • 생성한 버킷을 클릭한다

  • 속성 메뉴를 눌러 이동한다
 
  • 속성 메뉴 화면에서 페이지의 스크롤을 가장 아래로 내린다
  • '정적 웹 사이트 호스팅' 옵션이 보이면 '편집' 버튼을 클릭한다

  • 정적 웹 사이트 호스팅의 활성화/비활성화 여부를 묻는 창이 나온다
  • '활성화' 옵션을 선택한다

 
  • 활성화를 선택하면 여러 옵션을 추가로 변경할 수 있다
  • 인덱스 문서를 작성한다
    - 인덱스 문서 부분에는 웹 사이트 주소에 처음 접속했을 때 보일 기본 페이지를 지정한다
    - '인덱스 문서' 기입란에 'index.html'을 작성한다
  • '오류 문서' 부분은 공란으로 비워놔도 된다
    - 오류 발생 시 메인 페이지를 반환하기 위해서 'error.index.html'을 기입한다
  • '변경 사항 저장' 버튼을 클릭한다
 
  • 변경 사항이 저장되면, '정적 웹 사이트 호스팅을 편집했습니다'는 메시지와 함께 속성 메뉴 페이지로 리디렉션 된다
  • 페이지의 가장 아래로 스크롤을 내려서 방금 편집했던 정적 웹 사이트 호스팅 옵션 부분으로 이동한다
  • 예전에 존재하지 않았던 버킷 웹 사이트 엔드 포인트가 생성되었다
  • 해당 주소를 클릭한다

 
  • 에러 메시지를 확인할 수 있다
  • 이유는 버킷에 정적 웹 페이지 파일을 아직 업로드하지 않았고, 퍼블릭 액세스 설정 변경과 정책 생성을 하지 않았기 때문이다
 
  • 속성 메뉴 옆에 있는 '객체' 메뉴를 클릭해서 이동한다

  • 객체 메뉴로 이동하여 '업로드' 버튼을 클릭한다

  • build 폴더 안에 포함된 내용을 업로드 한다
    - client 폴더 안에 있는 build 폴더를 열어 안에 있는 파일을 모두 드래그하여 이동한다
 
  • 객체를 업로드하는 페이지에 필요한 파일을 드래그& 드랍 형식으로 업로드 할 수 있다
  • 주의 할 부분은 build 폴더 자체를 업로드하는 게 아닌 build 폴더 안에 저장된 파일만 업로드해야 한다
  • build 폴더 안에 있는 파일들이 업로드 대기 목록에 추가되었으면 '업로드' 버튼을 클릭한다
  • 업로드가 완료되면  '업로드 성공' 메시지를 확인할 수 있다
  • 확인 후 '닫기'를 클릭한다

 

 3-1) 퍼블릭 액세스 차단 옵션 해제 및 정책 생성

  • S3 메인 화면으로 이동하여 생성한 버킷을 클릭한다
  • '권한' 메뉴를 클릭한다
  • 권한 메뉴에서 '퍼블릭 액세스 차단(버킷 설정)'이라는 옵션에서 '편집' 버튼을 클릭한다
 
  • '모든 퍼블릭 액세스 차단' 옵션의 체크 박스를 해제하고  '변경 사항 저장' 버튼을 클릭한다

  • '변경 사항 저장' 버튼을 클릭하면 경고 창이 뜨는데, '확인'을 기입하고 확인을 클릭한다
  • 버킷 퍼블릭 액세스 차단을 변경하고 나면 다시 권한 메뉴로 리디렉션 된다
  • 퍼블릭 액세스 차단(버킷 설정) 옵션 밑에 있는 '버킷 정책' 옵션을 찾아서 '편집' 버튼을 클릭한다

 
  • '정책 생성기' 버튼을 클릭한다

 

  • 새 창이 뜨면서 AWS Policy Generator 페이지에 접속된다
  • 여기서 버킷 정책을 생성한다
  • 'Select Type of Policy' 옵션에서는 'S3 Bucket Policy'를 선택한다
  • 'Principal' 옵션은 권한을 적용할 사용자를 정한다
    - 모두에게 공개할 것이므로 *(별표)를 입력한다
  • 'Actions' 옵션에서는 'GetObject'를 선택한다
    - 버킷에 접근하는 모든 사용자가 버킷 내에 저장된 객체 데이터를 읽을 수 있다
    - 버킷을 웹 사이트 용도로 구성할 때 선택한다
  • ARN에는 앞서 작성한 버킷 이름을 정확하게 작성합니다. 예) arn:aws:s3:::practice-bucket-deploy/*

AWS Policy Generator

 
  • 'Generate Policy' 버튼을 누르면 정책이 생성된다

  • 정책은 JSON 형태로 생성된다
  • 생성된 정책을 드래그해서 복사한 뒤 Close 버튼을 누른다

 
  • 버킷 정책 편집 페이지로 돌아가서 복사한 Json 파일을 정책에 붙여 넣고 '변경 사항 저장' 버튼을 클릭한다
 
  • 모든 과정이 완료되었다
  • 성공적으로 완료되었는지 테스트하기 위해서 속성 메뉴로 이동한다
  • 정적 웹 사이트 호스팅 옵션을 찾은 뒤, 버킷 웹 사이트 엔드 포인트 주소를 클릭하여 테스트를 진행한다
  • 브라우저에 정상적으로 화면이 출력된다면 성공적으로 마무리된 것이다

1. 보안 그룹(Security Group)

  • 인스턴스로 들어가고 인스턴스에서 나가는 트래픽에 대한 가상 방화벽을 말한다
  • 인스턴스로 들어가는 트래픽을 인바운드라고 한다
  • 인스턴스에서 나가는 트래픽을 아웃바운드라고 한다
  • Security Group은 AWS에서 임대한 인스턴스의 가상 방화벽을 의미한다

 

2. 인바운드 규칙

  •  EC2 인스턴스로 들어오는 트래픽에 대한 규칙이다
  • 인바운드 규칙에 허용되지 않은 규칙은 인스턴스로 접근하지 못하도록 필터링 된다
  • EC2 인스턴스를 생성하면 기본적으로 SSH 접속을 위한 SSH 규칙만 생성되어 있다

 

3. 아웃바운드 규칙

  • EC2 인스턴스에서 나가는 트래픽에 대한 규칙이다
  • EC2 인스턴스를 생성하면 기본적으로 나가는 모든 트래픽을 허용한다

 

 

  • 인스턴스 탭의 우측에서 해당 인스턴스가 어떤 보안그룹에 속해 있는지 확인할 수 있다

  • 해당 인스턴스에서 좌측의 네트워크보안 > 보안그룹을 클릭하면 보안 설정창을 볼 수 있다

 

4. 인바운드 규칙 설정

  • 인바운드 규칙은 필요에 따라 규칙을 추가하고 제거하는 과정이 자유롭다
  • 보안그룹 ID에서 추가할 그룹을 선택한다

 

  • 우측의 인바운드 규칙 편집을 클릭하여 규칙을 추가한다

  • 규칙을 추가 / 삭제를 자유롭게 할 수 있다
  • 추가버튼을 클릭한다

  • EC2 인스턴스에서 실행중인 서버가 인터넷에서 요청을 받을수 있도록 인바운드 규칙을 설정한다
  • 인바운드유형과 허락하는 포트의 범위를 지정한다

  • 보안그룹은 소스에 따라 인바운드/아웃바운드 요청을 허락할 수도, 거절할 수도 있다
  • 소스는 사용자 지정, 모두 허용, 내IP 중에서 선택할 수 있다
  • '규칙 저장' 버튼을 누르면 보안 그룹을 설정하는 과정이 완료된다
  • 설정이 완료되면 브라우저 또는 Postman에 접속하여 정상적으로 응답을 받는지 테스트를 진행하여 확인한다
  • 인바운드 규칙 수정을 완료하면 8080 포트로 접속 할 수 있는 것을 브라우저에서 확인 할 수 있습니다.
  • 퍼블릭 IPv4 DNS or 퍼블릭 IPv4 주소:8080 으로 접속한다 

 

1. 인스턴스에 개발 환경 구축하기

  • EC2 인스턴스를 생성하는 것은 가상 PC 한 대를 임대하는 것과 같다
  • 컴퓨터를 처음 구입하면 필요한 프로그램을 설치하듯이, EC2 인스턴스에 처음 접속하면 서버를 구동하는 데 필요한 개발 환경을 구축해야 한다
  • EC2 인스턴스 연결에서 진행한 것과 같이 인스턴스를 연결한 터미널에 아래 명령어를 입력한다
  • 패키지 매니저가 관리하는 패키지의 정보를 최신 상태로 업데이트하기 위해서 아래 명령어를 사용한다
  • 터미널에 연결되면 bash 와 cd ~ 명령으로 디렉토리를 변경한 후에 진행한다
$ sudo apt update
  • 명령을 실행하면 업데이트가 진행된다

  • 업데이트가 끝나면 JAVA를 설치하는 명령을 입력하고 실행한다
$ sudo apt install openjdk-11-jre-headless
  • 함께 실습 중인 페어가 JAVA를 설치해 놓아서 이미 설치가 되었다는 안내가 나온다

  • 정상적으로 설치를 하게 되면 나오는 출력이다
  • 확인창이 나오면 'yes'를 입력한다

  • 설치가 완료되면 java -version 명령으로 java 라이브러리 버젼과 완료여부를 확인한다

 

2. git을 통해 서버 코드 클론 받기

  • 스프린트 코드가 저장된 깃헙 레포지토리 주소를 복사한다
    - 실습에서는 운영진에서 만들어 놓은 레포지토리의 주소를 복사한다

  • git clone 명령어를 통해 EC2 인스턴스에 스프린트 코드를 클론 받는다
    - 페어가 미리 생성해 놓아서 에러가 발생한다

  • 정상적으로 진행되는 출력은 아래와 같다

  • clone을 위해 설정하셨던것과 같이, SSH등록이 필요하다

※ SSH등록이 필요하므로 참조한다

 ▶ SSH 키 생성

  • ssh 키는 비대칭키로 구성되며, 그 이름에서 유추할 수 있듯이 두 개의 키가 서로 대칭이 되지 않는 형태로 존재한다
  • 다음의 명령어를 프롬프트에 입력하고, ssh 키 페어(쌍)을 생성한다
  • 명령어를 입력 후 Enter 키를 몇 번 입력하면, ssh 키 페어가 생성된다
ssh-keygen

  • ssh-keygen 명령어는 경로 ~/.ssh./ 에 두 파일 id_rsa 와 id_rsa.pub 를 생성한다
  • 두 파일은 ssh 키 페어라고 한다
  • id_rsa.pub는 누구에게나 공개해도 되는 공개키(Public Key) 라고 한다
  • id_rsa는 공개되면 안 되는 키라고 하여 개인키(Private Key) 또는 비밀키(Secret Key) 라고 한다
  • ssh 키 페어를 생성하였으므로, 생성된 키 페어 중 공개키를 복사하여 github에 등록한다

  • 정상적으로 클론했는지 확인하기 위해 터미널에 ls 명령어를 입력한다
  • 스프린트 코드 폴더명이 보이면 정상적으로 다운로드가 완료된 것이다

  • 터미널을 통해 스프린트 코드 안의 DeployServer 디렉토리로 이동한다
cd be-sprint-deployment/DeployServer

  • 빌드작업을 진행합니다.
./gradlew build
  • 진행율이 표시되면서 빌드가 진행된다

  • 터미널에 ls명령어를 입력하여 빌드 여부를 확인한다
  • 정상적으로 빌드가 완료되었으면 build폴더를 확인할 수 있다

  • 아래 명령어를 이용하여 빌드된 파일을 실행한다
java -jar build/libs/DeployServer-0.0.1-SNAPSHOT.jar
  • 아래와 같은 출력되면 정상적으로 서버가 실행되었음을 확인할 수 있다

 

 

3. EC2 인스턴스에서 서버 실행하기

  • 다음은 EC2 인스턴스의 IP 주소로 접근해서 테스트를 진행한다
  • IP 주소는 EC2 대시보드에서 생성한 EC2 인스턴스를 클릭하면 확인할 수 있다
  • 두 가지 형태의 주소가 존재하는 것을 확인할 수 있다
  • 퍼블릭 IPv4 주소와 퍼블릭 IPv4 DNS는 형태만 다를 뿐 같은 주소이다
  • 둘 중 어떤 주소를 사용해도 상관없다
  • 실습에서는 퍼블릭 IPv4 DNS 주소를 이용하여 접속 테스트를 진행한다

  • EC2 인스턴스의 IP 주소로 접속하면 오류가 발생한다
  • 오류 메세지가 보이는 이유는  '보안 그룹' 설정을 하지 않았기 때문이다

 

  • 웹 애플리케이션에 IP주소를 입력하면 접속되는 것을 확인할 수 있다


4. Spring Boot 백 그라운드 실행

  • EC2 인스턴스에서 서버 실행 시 java -jar로 시작하는 명령어로 실행하였을 때 foreground에서 동작을 해서 실행시킨 창을 항상 열어놔야 하는 단점이 있다
  • 향후 사용하게 될 수 있는 Jenkins 같은 곳에서 배포된 jar파일을 실행했을 경우에도 문제가 될 수 있다
  • 어플리케이션이 background에서 실행도 가능해야하고 어플리케이션이 제대로 실행 되고 있는지 체크를 해야한다면 이를 편하게 하기 위한 실행 스크립트인  "Shell Script" 를 만들어야 한다
  • 셀 스크립트는 셀이나 명령 중 인터프리터에서 돌아가도록 작성된 스크립트이다
    -  인터프리터는 프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경이다
  • 운영체제를 위한 스크립트이다
  • 셀 스크립트는 대략적 구조만 파악을 하고 필요에 따라 검색을 통해 필요한 셀 스크립트를 가져다 조합하면 된다
  • 셸 스크립트는 한 번 작성하면 재작성 하는 경우가 매우 적다
  • 여러 경우의 스크립트를 모아두어 상황에 맞게 사용하는 방법이 효율적이다

 1) 셀스크립트(Shell Script) 작성

  • 셸 스크립트는 실행 명령을 미리 작성해 둔 것이다
  • .sh 라는 파일 형태로 저장된다

  • 파일을 생성한다
$ nano restart.sh // 에디터로 파일을 생성한다

  • 아래 코드를 작성 후 저장한다
#!/bin/bash

ps -ef | grep "DeployServer-0.0.1-SNAPSHOT.jar" | grep -v grep | awk '{print $2}' | xargs kill -9 2> /dev/null

if [ $? -eq 0 ];then
    echo "my-application Stop Success"
else
    echo "my-application Not Running"
fi

echo "my-application Restart!"
echo $1
nohup java -jar build/libs/DeployServer-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev > /dev/null 2>&1 &

  • 아래 명령어를 통해 실행 권한을 부여한다
chmod 755 restart.sh
  • 아래 명령어를 통해 실행이 가능하다
    - 경우에 따라 권한 이슈로 실행이 되지 않을 경우에는 sudo를 붙여 실행하면 오류없이 실행된다
 ./restart.sh

출력되는 결과가 맞는지는 확인이 필요하다....ㅠㅠ

'클라우드' 카테고리의 다른 글

Cloud - AWS 실습 - 클라이언트 배포  (0) 2022.08.04
Cloud - AWS 실습 - Security Group  (0) 2022.08.03
Cloud - AWS 실습 - EC2 인스턴스 생성/연결  (0) 2022.08.02
Cloud - AWS(Amzon Web Service)  (0) 2022.08.02
Cloud - 기본  (0) 2022.08.02

1. 사전 정보 확인

 

https://console.aws.amazon.com/console/home

 

console.aws.amazon.com

 

2. 진행 순서

 1) 서버 배포 (EC2)

  • EC2 콘솔을 통해 EC2 인스턴스를 생성한다
  • 간단한 서버 애플리케이션을 생성하고 EC2 인스턴스에 코드를 배포한다
  • 서버를 실행시키고 브라우저에서 서버에 접속할 수 있어야 한다

 2) 클라이언트 배포 (S3)

  • S3 콘솔을 통해 버킷을 생성한다
  • 클라이언트 파일을 빌드하고 결과물을 버킷에 업로드 한다
  • 정적 웹 호스팅 기능을 이용하여 클라이언트 코드를 배포한다

 3) 데이터베이스 연결 (RDS)

  • RDS 콘솔을 통해 RDS 인스턴스를 생성한다
  • 로컬 터미널 혹은 EC2 인스턴스가 실행되고 있는 터미널을 통해 RDS 인스턴스에 연결한다

 4)Getting Started

  • 스프린트는 JWT 인증 방식을 사용한다
  • 발급된 JWT 토큰을 Local Storage에 저장하는 방식을 사용하면 웹 사이트 주소에 HTTPS를 적용할 필요가 없다
  • 쿠키와 세션을 인증 방식으로 사용하는 서비스를 배포하기 위해서는 추가적인 AWS 서비스를 사용해야 하며 소액의 과금이 발생할 수 있다
  • application.properties 파일을 보며 어떤 환경변수들이 정의되어 있는지 확인한다 (실습 진행 시 주요한 역할을 한다)

 5) 서버 배포 완료 확인 방법

  •  EC2 인스턴스를 통해서 서버를 실행한 뒤 Postman을 이용해 테스트를 진행한다
    - 서버 배포 성공 시 'hello World' 응답이 출력된다

 

 6) 클라이언트 배포가 완료되었을 때 확인 방법

  • 생성한 버킷의 엔드포인트 주소에 접속하여 테스트를 진행한다
  • 이름 입력란에 "김코딩"을, 비밀번호 입력란에 "1234"를 입력하여 로그인 테스트를 진행한다
  • 클라이언트, 서버 배포 성공 시 로그인을 하면 메인 페이지로 이동할 수 있다

 

 

 7) RDS 연결을 완료했을 때 확인 방법

  • EC2 인스턴스 서버에 데이터베이스를 연결한 뒤, S3 버킷의 엔드포인트 주소에 접속하여 테스트를 진행한다

 

3. EC2 인스턴스 생성/연결

 1) 인스턴스 생성

  •  EC2를 선택한다
  • 처음에는 표시가 없을 수 있으니 AWS 메뉴에서 EC2 서비스를 검색하고 접속한다

  • 리전을 서울로 변경한다
  • 인스턴스 생성을 시작한다

 
 
  • 생성할 인스턴스의 이름을 설정한다
  • 해당 인스턴스를 알아볼 수 있도록 적절할 이름을 작성한다
  • 작성한 이름은 인스턴스의 이름임과 동시에 인스턴스의 태그로 자동 설정된다
  • Key : Name    /    Value : (지금 작성한 인스턴스의 이름)
  • 설정을 빈칸으로 남겨둘 수도 있으며 인스턴스가 생성된 이후 대시보드에서 설정할 수 있다
 
  • 용도에 맞는 AMI를 선택한다
  • 실습 과정에서는 ubuntu 인스턴스를 생성한다
  • 프리 티어 사용 가능 태그를 확인하여 과금이 되지 않도록 유의한다
  • ubuntu 인스턴스를 생성할 때 20버전 이하를 권장한다(배포 자동화 실습 때 22 버전은 이슈가 발생할 수 있다)

 

  • 인스턴스 유형을 선택한다

 

  • 생성하는 인스턴스의 CPU, RAM, 용량에 대한 선택이 가능하다
  • 실습 과정에서는 프리 티어로 사용 가능한 t2.micro를 선택한다
  • 리전에 따라 프리 티어로 사용 가능한 인스턴스 유형이 상이할 수 있다

 

  • 인스턴스 유형을 선택하면 키 페어를 연결할 수 있는 항목이 보인다
  • '새 키페어 생성'을 클릭하여 새로운 키 페어를 생성한다

 

  • 생성되는 인스턴스를 원격으로 제어하기 위해서는 SSH 연결을 통한 원격접속이 필요하다
  • 원격접속을 위해서 필요한 Key를 생성하고 다운로드 해야 한다
  • SSH 프로토콜은 서로 다른 PC가 인터넷과 같은 Public Network를 통해 통신을 할 때 보안상 안전하게 통신을 하기 위한 통신 규약이다
    - 주고받는 데이터를 암호화해서 해당 키 페어를 가지지 않은 사람은 통신되는 데이터를 알아볼 수 없기 때문에 보안상 안전한 통신 방법이다
  • 인스턴스 생성 마지막 단계에서 다운로드 한 파일은 SSH 통신을 위한 키 페어 중 프라이빗 키가 기록된 파일이다
    - .pem 확장자를 가지고 있다
    - 해당 키 페어 파일은 EC2 인스턴스에 연결을 할 때 사용하는 암호가 담긴 파일이다
    - .pem 파일은 관리에 유의해야 한다

 

  • 현재는 실습용으로 로그인 하였으므로 키 페어가 생성되어 있기 때문에 에러가 발생한다

  • 발행되어 있는 키 페어를 선택한다

 

  • 많은 인스턴스가 생성되어 있는 경우 인스턴스 ID를 통해 인스턴스를 구분할 수 있다
  • 실습 과정 중 인스턴스의 이름을 작성하지 않은 경우, 인스턴스 ID를 반드시 기억한다
  • 우측 하단의 '모든 인스턴스 보기' 버튼을 클릭하여 생성한 인스턴스를 확인한다
 
 
  • 인스턴스 생성 후 리소스 이름을 검색하면 배정받은 리소스에 빠르게 접근할 수 있다
  • 인스턴스를 선택하면 하단에 세부 정보를 볼 수 있다
  • 인스턴스 생성 단계에서 인스턴스의 이름을 작성하면 화면의 Name 컬럼에 ‘ - ‘ 대신 설정한 이름값을 확인할 수 있다
  • 이름을 설정하지 않았을 경우 인스턴스 ID를 통해 인스턴스를 구분할 수 있다
    - 생성된 인스턴스가 많아지는 경우 알아보기 힘들 수 있다

 

  • 이름을 설정하지 않은 경우에는 아래와 같이 이름을 편집할 수 있다
  • Name 컬럼에서 해당 인스턴스의 이름을 알아보기 쉽도록 설정해두면 각 인스턴스를 구분하기 용이하다

 

 2) 인스턴스 연결(Session Manager)

  • 인스턴스에 연결하기 위해 인스턴스ID를 클릭한다

 

  • 인스턴스 요약 정보가 출력된다
  • 연결을 클릭한다

 

  • Session Manager 을 열고 연결을 클릭한다

 

  • 세션 매니저를 이용하여 접속하면 웹 브라우저 환경에서 터미널이 실행된다
  • 터미널을 bash 로 변경한다

 

 2) 인스턴스 연결(SSH)

  • 생성한 인스턴스에 원격접속하여 원격으로 인스턴스를 제어할 수 있다
  • 인스턴스에 원격접속을 하기 위해서 필요한 것이 인스턴스를 생성할 때 다운로드한 키 페어 파일(.pem)이다
  • SSH 연결을 통해서 인스턴스에 접속한다

 

  • 인스턴스에 연결하기 위해 인스턴스ID를 클릭한다

 

  • 인스턴스 요약 정보가 출력된다
  • 연결을 클릭한다

 

  • 로컬 터미널에서 SSH 프로토콜을 이용해서 인스턴스와 연결이 가능하다
  • 먼저 다운로드했던 키 페어 파일(.pem)의 권한을 수정해야 한다
    - chmod는 Linux의 기본 명령어로 change mode의 약어이며 해당 명령을 통해 파일에 대한 권한 설정이 가능하다
    - 400은 4(소유자), 0(그룹), 0(전체)을 의미한다
    - 권한은 읽기(4), 쓰기(2), 실행(1)로 구성되어 있다
      : 400은 소유자에게 읽기 권한만 부여한 것이다
      : 700(4+2+1)을 부여하면 소유자에게 읽기 + 쓰기 + 실행의 권한이 부여된다
  • chmod 400 AWS_Deploy_kim.pem 은 AWS_Deploy_kim.pem 파일에 대해 소유자에게 일기 권한을 부여한다는 뜻이다

 

  • 키 페어 파일(.pem)의 권한을 수정하지 않은 경우 권한이 너무 open되어있다는 경고 메시지와 함께 접속이 거절된다

 

  • chmod 명령어를 이용해 다운로드한 키 페어 파일(.pem)의 권한을 수정한다
  • 디렉토리 이동 후 진행하거나, 혹은 chmod 명령어를 실행하며 경로/파일이름 으로 실행할 수 있다
    - chmod 400 ~/Downloads/AWS_Deploy_Practice.pem

 

  • 파일 권한을 설정했다면 ssh 명령어를 통해 인스턴스에 접속할 수 있다

 

  • ssh 접속을 위한 주소는 인스턴스를 클릭하면 출력되는 세부 정보 탭에서도 확인할 수 있다

 

  • ssh 주소의 구조와 의미는 아래와 같다

 

  • 터미널에 ssh key를 입력하고 연결한다

 

  • yes 를 입력한다
    - 경고문구와 함께 다른 세션이 종료되었음을 출력한다

      - SSH 접속으로 인하여 웹 세션이 종료되었음을 확인할 수 있다

 

  • 다시 접속하면 실습용이므로 권한이 없어서 액세스를 거부당한다

 

  • 정상적으로 연결되면 아래와 같이 출력된다

 

'클라우드' 카테고리의 다른 글

Cloud - AWS 실습 - 클라이언트 배포  (0) 2022.08.04
Cloud - AWS 실습 - Security Group  (0) 2022.08.03
Cloud - AWS 실습 - EC2 인스턴스 서버 실행  (0) 2022.08.03
Cloud - AWS(Amzon Web Service)  (0) 2022.08.02
Cloud - 기본  (0) 2022.08.02

출처 : https://www.acmicpc.net/

 

Baekjoon Online Judge

Baekjoon Online Judge 프로그래밍 문제를 풀고 온라인으로 채점받을 수 있는 곳입니다.

www.acmicpc.net

1. 문제

/*
상현이가 가르치는 아이폰 앱 개발 수업의 수강생은 원섭, 세희, 상근, 숭, 강수이다.
어제 이 수업의 기말고사가 있었고, 상현이는 지금 학생들의 기말고사 시험지를 채점하고 있다.
기말고사 점수가 40점 이상인 학생들은 그 점수 그대로 자신의 성적이 된다.
하지만, 40점 미만인 학생들은 보충학습을 듣는 조건을 수락하면 40점을 받게 된다.
보충학습은 거부할 수 없기 때문에, 40점 미만인 학생들은 항상 40점을 받게 된다.
학생 5명의 점수가 주어졌을 때, 평균 점수를 구하는 프로그램을 작성하시오.

입력
입력은 총 5줄로 이루어져 있고, 원섭이의 점수, 세희의 점수, 상근이의 점수, 숭이의 점수, 강수의 점수가 순서대로 주어진다.
점수는 모두 0점 이상, 100점 이하인 5의 배수이다. 따라서, 평균 점수는 항상 정수이다.

출력
첫째 줄에 학생 5명의 평균 점수를 출력한다.

예제 입력 1
10
65
100
30
95
예제 출력 1 : 68

힌트
숭과 원섭이는 40점 미만이고, 보충학습에 참여할 예정이기 때문에 40점을 받게 된다. 따라서, 점수의 합은 340점이고, 평균은 68점이 된다.

출처
Olympiad > Japanese Olympiad in Informatics > Japanese Olympiad in Informatics Qualification Round > JOI 2014 예선 1번
 */

 

2. 풀이

import java.io.IOException;
import java.util.Scanner;

public class AverageScore_me {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        int sum = 0;
        int average = 0;

        for (int i = 0; i < 5; i++) {

            int score = sc.nextInt();

            if (score > 40) {
                score = score;
            } else if (score == 40) {
                score = score;
            } else if (score < 40) {
                score = 40;
            }
            System.out.println("환산 점수는 = " + score);
            sum = sum + score;
            average = sum / 5;
        }
            System.out.println("5명의 평균 점수는 = " + average);

        }
    }

 

3. 백준 채점용 코드

// import java.io.IOException;
import java.util.Scanner;

public class AverageScore_me {
    public static void main(String[] args) /* throws IOException */ {
        Scanner sc = new Scanner(System.in);
        int sum = 0;
        int average = 0;

        for (int i = 0; i < 5; i++) {

            int score = sc.nextInt();

            if (score > 40) {
                score = score;
            } else if (score == 40) {
                score = score;
            } else if (score < 40) {
                score = 40;
            }
            // System.out.println("환산 점수는 = " + score);
            sum = sum + score;
            average = sum / 5;
        }
            System.out.println(/* "5명의 평균 점수는 = " + */ average);

        }
    }

'예제 > Java 예제' 카테고리의 다른 글

Java 예제 - 원금의 두배 구하기  (0) 2022.09.15
Java 예제 - 배열과 HashMap  (0) 2022.09.15
Java - HashMap 활용 - Id,Pw 확인  (0) 2022.09.14
배열의 합 구하기  (0) 2022.08.14
coding test - 성적처리  (0) 2022.05.22

1. EC2(Elastic Compute Cloud)

 1) 개요

  • 아마존 웹 서비스에서 제공하는 클라우드 컴퓨팅 서비스이다
  • 클라우드 컴퓨팅은 인터넷(클라우드)을 통해 서버, 스토리지, 데이터베이스 등의 서비스를 제공하는 시스템이다
  • 사용한 만큼의 비용을 지불하기 때문에 '탄력적인'이라는 의미의 Elastic 단어를 사용한다
  • 비용적인 부분뿐만이 아니라 필요에 따라 성능, 용량을 자유롭게 조절할 수 있다
  • EC2 서비스는 AWS에서 비용, 성능, 용량 면에서 탄력적인 클라우드 컴퓨터를 제공하는 서비스라고 할 수 있다

 2) 장점

  • 구성하는 데 필요한 시간이 짧다
    - PC를 구매할 경우 배송까지의 시간이 필요하지만 EC2 서비스는 몇 번의 클릭만으로 PC를 구성할 수 있다
  • AMI를 통해서 필요한 용도에 따라 다양한 운영체제에 대한 선택이 가능하다
    - EC2에서는 AMI라는 다양한 템플릿을 제공한다
    - 필요에 따라 손쉽게 운영체제를 선택하고 구성할 수 있다
    - CPU와 RAM, 용량도 쉽게 구성할 수 있다

 3) Instance

  • EC2는 컴퓨터를 한 대 빌리는 것이므로 컴퓨터로 할 수 있는 모든 일을 할 수 있다
  • 아마존에서 전 세계에 만들어 놓은 데이터 센터(인프라)에 컴퓨터가 위치하고 있다
  • 컴퓨터를 조작하기 위해 네트워크(인터넷)를 통해서 컴퓨터를 제어해야 한다
  • EC2를 통해 웹서버를 설치하고 웹 서버를 통해서 사용자가 웹 브라우저를 통해 요청하는 서비스를 제공할 수 있다
  • 인스턴스는 1대의 컴퓨터를 의미하는 단위이다
  • AWS에서 컴퓨터를 빌리는 것을 인스턴스를 생성한다고 한다

 4) AMI(AmazonMachine Image)

  • 소프트웨어 구성이 기재된 템플릿이다
  • 템플릿 종류로는
    - 단순히 운영체제(윈도우, 우분투 리눅스 등)만 깔려있는 템플릿과
    - 특정 런타임이 설치되어 있는 템플릿이 제공된다(우분투 + node.js, 윈도우 + JVM 등)
  • 사용 용도에 맞게 운영체제, 런타임 등이 구성된 템플릿을 선택할 수 있다 
 
  • Instance는 선택한 AMI를 토대로 구성된다
  • AWS에는 상당히 많은 양의 AMI 세팅이 준비되어 있어서 쉽게 인스턴스의 운영체제를 구성할 수 있다
  • 세팅되어 있는 AMI 이외에도 필요에 따라 직접 AMI를 구성할 수도 있다
 

 

2. RDS(Relational Database Service)

  • AWS에서 제공하는 관계형 데이터베이스 서비스이다
  • EC2 인스턴스에 관계형 데이터베이스 엔진을 설치하여 관리하면...
    - 사용자가 일일이 시간을 투자하여 데이터베이스 엔진의 설치와 버전 관리, 데이터 백업을 해야한다
    - 가용성과 내구성이 확보되지 않기 때문에 데이터베이스에 저장된 데이터가 유실되거나 정상적으로 사용하지 못할 확률이 크다
    - 필요에 따라 데이터베이스의 규모를 확장하기 어렵다
  • RDS를 통해 데이터를 관리하면...
    - 데이터베이스 유지 보수와 관련된 일들을 RDS에서 자동으로 관리한다
    - 사용자는 초기 설정을 제외과 데이터베이스에 저장된 데이터만 관리하면 된다
    - 다양한 데이터베이스 엔진 선택지를 제공한다
      : 실무자는 회사에 필요한 데이터베이스 엔진을 취사선택하여 이용할 수 있다
      : 일반 사용자는 필요와 목적에 맞게 데이터베이스 엔진을 선택하여 효율성을 높일 수 있다
  EC2 인스턴스에 Database 설치 RDS로 데이터 관리
기반시설 구축 AWS AWS
운영체제 설치/관리 AWS AWS
데이터베이스 설치/관리 사용자 AWS
데이터 백업 사용자 AWS
가용성, 내구성 확보 사용자 AWS
데이터베이스 규모 확장 사용자 AWS
 

3. S3(Simple Storage Service)

  •  AWS에서 제공하는 클라우드 스토리지 서비스이다
 1) 클라우드 스토리지
  • 인터넷 공간에 데이터를 저장하는 저장소를 말한다
  • 컴퓨터 부품으로 비유하면 하드디스크의 역할을 하는 서비스이다
  • 구글의 Google Drive, 네이버의 MYBOX, 마이크로소프트의 Onedrive와 같은 서비스이다
  • 클라우드 스토리지 서비스의 장점
    - 뛰어난 접근성을 가지고 있다
    - 웹 환경이라면 언제 어디서나 저장된 파일에 접근할 수 있다
    - 컴퓨터 외에 웹에 접속이 가능한 전자기기를 활용하여 클라우드 스토리지에 저장된 데이터에 접속할 수 있다

 2) S3 장점

  • 뛰어난 접근성을 가지고 있다
  • 높은 확장성을 가지고 있어서 많은 시간과 수고를 들이지 않고 스토리지 규모를 확장/축소할 수 있다
  • 스토리지의 용량을 무한히 확장할 수 있다
  • 사용한 만큼만 비용을 지불하면 되므로 비용적인 측면에서 매우 효율적이다
  • 99.999999999%의 내구성을 보장하기 때문에 저장된 파일의 유실 가능성이 적다
    - S3에 저장된 파일을 잃어버릴 확률보다, 길을 걷다가 벼락을 맞을 확률(약 0.0000007%의 확률)이 700배나 높다
  • 연간 99.99%의 스토리지 가용성을 보장하도록 설계가 되어 있다
    - 파일들을 정상적으로 사용할 수 있는 시간이 길어진다
    - 1년 동안 S3에 파일을 저장했을 시, 8.76 시간 동안만 스토리지 이용에 장애가 발생할 수도 있다
  • 다양한 스토리지 클래스를 제공한다
    - 저장소를 어떤 목적으로 활용할지에 따라 효율적으로 선택할 수 있는 스토리지 클래스가 달라진다
    - 대표적으로 많이 선택하는 스토리지 클래스는 Standard 클래스와 Glacier 클래스이다
      : Standard 클래스는 범용적인 목적으로 사용하기에 적합하다
        - 데이터에 빠른 속도로 접근할 수 있고, 데이터 액세스 요청에 대한 처리 속도가 빠르다
        - 보관 비용이 높게 발생하기 때문에 데이터를 오래 보관하는 목적으로는 비효율적이다
  • 정적 웹 사이트 호스팅이 가능하다
    - 정적 파일은 서버의 개입 없이 생성된 파일이다
    - 클라이언트가 서버에 요청을 보내면, 서버가 요청에 맞추어 생성한 파일을 '동적' 파일이라고 한다
    - 웹 호스팅(Web Hosting)이란 서버의 한 공간을 임대해 주는 서비스이다
     : 개인 또는 단체는 웹 호스팅 업체가 제공하는 서버의 한 공간을 빌려서 원하는 서비스를 배포할 수 있다
     : S3에서는 버킷이 사용자들이 정적 웹 사이트를 배포할 수 있는 공간을 제공한다
     : 버킷이라는 저장 공간에 정적 파일을 업로드하고 버킷을 정적 웹 사이트 호스팅 용도로 구성하면 정적 웹 사이트를 배포할 수 있다

 3) 리전(Region)

  • AWS에서 클라우드 서비스를 제공하기 위해서 운영하는 물리적인 서버의 위치이다
  • 지도의 주황색 동그라미 안에 숫자가 리전에 위치한 가용 영역의 수이다
  • 가용 영역(Availability Zone)은 각 리전 안에 존재하는 데이터 센터(IDC)를 말한다
  • 가용 영역은 각각 개별적인 위치에 존재한다
  • 한 곳의 가용 영역이 재난이나 사고로 인해 가동이 불가능해지더라도 다른 가용 영역에 백업을 해놓은 데이터를 활용하여 문제없이 서버가 가동되게 한다
  • 리전의 가동 방식을 통해 AWS에서 제공하는 서비스들의 높은 가용성과 내구성을 보장한다
리전 분포도
 
 
 4) 버킷(Bucket)
  • S3에 저장되는 파일들이 담기는 바구니이다
  • 파일을 저장하는 최상위 디렉터리이다
  • S3에서 저장되는 모든 파일은 버킷 안에 저장된다
  • 버킷에는 무한한 양의 파일을 저장할 수 있다
  • 각각의 버킷은 이름을 가지고 있다
  • 버킷의 이름은 버킷이 속해 있는 리전(버킷이 생성된 지역)에서 유일해야 한다
  • 버킷 정책을 생성하여 해당 버킷에 대한 다른 유저의 접근 권한을 수정할 수 있다

 5) 객체(Object)

  • S3에서 버킷에 담기는 파일을 객체라고 한다
  • 저장소에 데이터를 저장할 때 키-값 페어 형식으로 데이터를 저장한다

객체는 파일과 메타데이터로 구성된다
- 파일은 키-값 페어 형식으로 데이터를 저장한다
  : 파일의 값에는 실제 데이터를 저장한다
    - S3 객체의 값으로써 저장될 수 있는 데이터의 최대 크기는 5TB이다
  : 파일의 키는 각각의 객체를 고유하게 만들어주는 식별자 역할을 한다
    - 파일의 키를 이용하여 원하는 객체를 검색할 수 있다
- 메타데이터는 객체의 생성일, 크기, 유형과 같은 객체에 대한 정보가 담긴 데이터이다
  : 모든 객체는 고유한 URL 주소를 가지고 있다
    - URL 주소는 http://[버킷의 이름].S3.amazonaws.com/[객체의 키]의 형태로 구성된다
    - URL 주소를 통해서도 원하는 데이터에 접근할 수 있다

 

4. 배포

  • 개발한 서비스를 사용자가 이용할 수 있도록 하는 것을 배포라고 한다
  • 개발한 서비스를 사용자들에게 Client를 어떻게 제공할지 그리고 Client를 받은 사용자들이 서비스를 이용하기 위한 요청을 처리할 Server를 어떻게 제공할 것인지 Server의 데이터를 저장하고 제공할 Database는 어떻게 제공할 것인지를 결정해야 한다

 1) Client 코드 제공

  • AWS에서 제공하는 서비스인 S3라는 서비스를 통해 사용자들에게 Client를 제공할 수 있다
  • 로컬 환경에서는 자체 개발 서버 (예, create-react-app)를 이용해서 클라이언트 앱을 실행시킨다
  • 클라이언트를 위해서 EC2 인스턴스를 사용할 필요는 없다
    - 클라이언트 앱을 정적 파일로 빌드하여 제공하므로 S3를 이용해서 클라이언트를 배포한다

 2) 빌드(Build)

  • 불필요한 데이터를 없애고, 여러 갈래로 퍼져있는 데이터들을 통합/ 압축하여 배포에 최적화된 상태를 만드는 것이다
  • 빌드 과정을 진행하면 데이터의 용량이 줄어들고, 웹 사이트의 로딩 속도가 빨라진다
  • 일반적인 의미의 빌드는, 소스코드를 실행 가능한 번들로 변환하는 컴파일 과정을 의미한다
  • 웹 앱에서와같이 HTML, CSS, JS의 형태로 배포하는 경우는 조금 다르다
    - 웹 앱은 배포 가능한 정적 파일(static files)의 형태로 만들어야 한다
    - asset 자체가 정적인 경우에는 그대로 배포하면 된다
    - React의 경우에는 npm run build와 같은 명령을 사용하여 정적 파일 형태의 결과물을 만들어 낸 후 배포한다
    - 사용하고 있는 환경에 따라 빌드 과정은 다를 수 있다

 3) 데이터 제공

  • AWS에서 제공하는 CDN 서비스인 CloudFront를 통해서 각지의 데이터센터에 데이터를 분산시켜서 저장한다
  • AWS는 사용자와 가까운 지역의 센터에서 데이터를 주는 방식으로 사용자에게 빠른 서비스를 제공한다

 4) 도메인 접속

  • 지금까지 이용했던 서비스는 www.google.com과 같은 도메인 주소를 이용해서 접근하였다
  • google 사이트에 접속하기 위해서 172.217.151.228이라는 IP주소를 입력하지는 않았다
  • S3, EC2를 이용해서 배포된 서비스는 IP주소 혹은 AWS에서 제공하는 서비스와는 전혀 상관없는 긴 도메인주소를 통해 접근하게 된다
  • TodoList 서비스를 제공할 경우
    - www.todolist.ap-northeast-2.compute.amazonaws.com 주소보다는 www.todolist.com주소 일 때
    - 직관적으로 서비스를 이해할 수 있고 짧은 주소를 통해 서비스에 접근할 수 있다
  • AWS에서 제공하는 Route 53 서비스를 이용하면 직관적인 도메인 주소를 통해서 서비스에 접근하도록 할 수 있다

 

■ Cloud Computing

 ▶ 인터넷(클라우드)을 통해 서버, 스토리지, 데이터베이스 등의 컴퓨팅 서비스를 제공하는 서비스

1. AWS 실습 기준

Name AWS 분류 기준 유닛 기준 모델 및 클래스
EC2 컴퓨팅 AWS t2.micro
S3 스토리지,서버리스 AWS S3 Standard
RDS 데이터베이스 서비스 AWS MySQL, db.t2.micro
CodeBuild 개발자 도구 배포 자동화  
CodeDeploy 개발자 도구 배포 자동화  
CodePipeline 개발자 도구 배포 자동화  
IAM 보안 자격 증명 및 규정 준수 AWS  
System Manager 보안 자격 증명 및 규정 준수 배포 자동화  

 

2. 서버 방식

 1) 기존 서버방식(온프레미스 방식)

  •  주기적인 관리가 필요한다
    - 서버실에는 종종 고장이 나거나 인터넷과 연결이 되지 않는 컴퓨터가 생기기도 한다
    - 문제를 해결하기 위한 인력 및 비용이 투입되야 한다
    - 관리해야 하는 컴퓨터 및 다른 전자기기의 수가 많아지는 만큼 투입되어야 하는 인력 및 비용이 증가한다
  • 공간의 한계가 있다
    - 서버실이라는 공간에 컴퓨터를 배치하고 컴퓨터를 추가하는 방식으로 수용 능력을 향상한다
    - 공간이 부족하여 컴퓨터를 더는 배치할 수 없는 문제가 발생한다
    - 서버의 컴퓨팅 능력을 늘리려는 방법은 컴퓨터의 성능을 높이고 부피를 줄여 더 많은 컴퓨터를 같은 공간에 배치하는 방법으로 해결한다

 2) 클라우드 방식

  • 물리적인 컴퓨터가 아닌, 가상 컴퓨터를 대여하는 방식이다
  • 가상화(Virtualization) 기술의 발전으로부터 비롯되었다
  • 장점
    - 필요할 때마다 컴퓨팅 능력을 유연하게 조절할 수 있다
    - 고정적인 비용이 들어가는 온프레미스와는 달리 사용한 만큼의 요금만 지불한다
    - 컴퓨터의 스냅샷("이미지"라고 한다) 을 이용해 다른 컴퓨터로 즉시 이주(migration)가 가능하다
  • 단점
    - 운영 환경 자체가 클라우드 제공자에게 종속된다
    - 클라우드 서비스에 문제가 생기면 내가 배포하고 관리하는 환경에도 영향을 준다
    - 백엔드 구성 자체가 특정 회사의 기술로만 구성해야 한다
    - AWS와 같은 대표적인 클라우드 기술을 학습해야 한다

3. 클라우드 서비스 종류

 1) SaaS

  • Software as a Service
  • 클라우드 제공자가 당장 사용 가능한 소프트웨어를 제공하는 경우 대부분 SaaS에 해당한다

 2) PaaS

  • Platform as a Service
  • 클라우드 제공자가 데이터베이스, 개발 플랫폼까지 제공하는 경우 대부분 PaaS에 해당한다

 3) IaaS

  • Infrastructure as a Service
  • 클라우드 제공자가 가상 컴퓨터까지 제공하는 경우 대부분 IaaS에 해당한다
  Network Hardwear OS Flatform Database Application
SaaS O O O O O O
PaaS O O O O X X
IaaS O O X X X X

 

4. 배포(Deploy)

 1) 배포

  • 개발한 서비스를 사용자들이 이용 가능하게 하는 일련의 과정이다
  • 기본적으로 4단계를 거쳐 개발한 서비스를 배포한다
  • 개발 환경과 배포 환경의 차이에 따라 환경 설정을 코드와 분리하여야 한다
Development Intergation Staging Production
로컬 환경에서 개발 및 테스트


변경사항 문제되지 않음


샘플 데이터를 이용하여 개발
각자 개발한 부분을 통합


코드간 충돌 여부 확인


코드간 문제발생 여부 확인
복제 데이터를 이용하여 테스트 실행

서비스제공 환경과 유사환경에서 테스트 실행

관계자와의 상호 검증
실제 데이터를 이용하여 실행


개발환경과 구분된 환경 제공


실제 서비스 제공 단계

 

  2) 환경변수(environment variable,  envvar, env)

  • 작성한 코드가 다른 환경에서 정상 작동할 수 있도록 설정을 환경 변수에 저장한다
  • 환경 변수는 코드 변경 없이 배포 때마다 쉽게 변경할 수 있다
  • 오류가 발생해도 코드 저장소에 올라갈 가능성이 낮다
  • 작성 코드는 절대 경로가 아닌 상대 경로를 사용한다
  • `.properties` 등을 이용해 환경 변수를 설정한다
  • 개발 환경을 통일할 수 있도록 docker와 같은 가상화 도구를 사용하여 환경 자체를 메타데이터로 담는다

 

3) 배포 플랫폼

 
 
 
 

 

 

1. 기본 환경 세팅

https://coding-mid-life.tistory.com/71 을 기본으로 시작한다

 

 2. OAuth2에 필요한 추가 코드

  1) build.gradle 파일에 코드를 추가한다

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // 추가

 2) application.yml 파일을 변경한다

  • clientID와 clientSecret은 구글 API console에서 발급받은 API Key를 입력한다
spring:
	security:
    oauth2:
      client:
        registration:
          google:
            clientId:
            clientSecret:
            scope:
              - email
              - profile

 3) loginForm.html 파일에 코드를 추가한다

</form>
<a href="/oauth2/authorization/google">구글 로그인</a> <!-- 추가 -->
<a href="join">회원가입</a>

 4) SecurityConfig 클래스에 코드를 추가한다

                .defaultSuccessUrl("/") //추가
                .and() // 추가
                .oauth2Login() // 추가
                .loginPage("/login"); // 추가
        return http.build();

 

 

3. 애플리케이션 실행

 1) 코드를 모두 수정한 후 애플리케이션을 실행한다

  • localhost:8080/login 으로 접속하여 '구글로그인'을 선택한다

계정을 클릭하여 로그인을 진행하면 

 

 

4. 구글 회원 프로필 정보 받기

 1) 로그인 후 필요한 후처리 작업을 해 준다

  • 코드 받기(인증)
  • 액세스 토큰(권한)
  • 사용자 프로필 정보를 가져온다
  • 3번에서 가져온 정보를 토대로 자동으로 회원가입 할 수 있다
  • 위 정보만으로 부족할 경우 추가적인 구성 정보를 기입할 수 있다

 2) config 패키지에 oauth 패키지를 만들고 PrincipalOauth2UserService 클래스를 생성한다

  • 구글 로그인 버튼 클릭 → 구글 로그인 창 → 로그인 완료 → code 리턴(OAuth-Client 라이브러리) → AccessToken 요청 userRequest 정보 → loadUser 함수 호출 → 구글로부터 회원프로필 정보를 받아 온다
  • 로그인 후에 필요한 후처리 작업을 담당한다
  • loadUser 메서드를 Override하는데 이 메서드는 구글로부터 받은 userRequest 데이터에 대한 후처리 함수이다
    - userRequest에 담긴 정보를 확인할 수 있는 메서드
    - userRequest.getClientRegistration()
    - userRequest.getAccessToken().getTokenValue()
    - super.loadUser(userRequest).getAttributest()
package com.memberlogin.loginjoin.config.oauth;

import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println("userRequest : " + userRequest);
        return super.loadUser(userRequest);
    }
}

 

 3) SecurityConfig에 위 코드들을 적용해 주는 코드를 추가한다

  @Autowired
    private PrincipalOauth2UserService principalOauth2UserService; // 추가
......
    	.userInfoEndpoint() // 추가
        .userService(principalOauth2UserService); // 추가

 

 4) Member 클래스에 코드를 추가한다

	private String provider;
    private String providerId;

 

 

5. authentication 정보 확인

 1) PrincipalDetails 클래스에 @Data 애너테이션을 추가한다

@Data // 추가

 

 2) IndexController 클래스에 코드를 추가한다

  • Authentication 객체를 의존성 주입을 통해 다운 캐스팅하여 정보를 가져올 수 있다.
  • 회원가입 → 로그인 후 /loginTest url로 접속해서 로그에 Authentication 정보를 확인합니다.
// 추가
		@GetMapping("/loginTest")
    public @ResponseBody String loginTest(Authentication authentication) {
        System.out.println("============/loginTest===========");
        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        System.out.println("authentication : " + principalDetails.getMember());
        return "세션 정보 확인";
    }

  • 위와 같은 동작을 애노테이션으로 할 수 있도록 코드를 추가한다
    - 애너테이션을 통해 회원정보를 가져올 수 있다.
// 추가
		@GetMapping("/loginTest2")
    public @ResponseBody String loginTest2(@AuthenticationPrincipal PrincipalDetails principalDetails) {
        System.out.println("============/loginTest2===========");
        System.out.println("userDetails : " + principalDetails.getMember());
        return "세션 정보 확인2";
    }

  • OAuth2 로그인 정보를 가져오기 위한 코드를 추가한다
// 추가
		@GetMapping("/loginTest3")
    public @ResponseBody String loginOAuthTest(
					Authentication authentication,
					@AuthenticationPrincipal OAuth2User oauth) {
        System.out.println("============/loginOAuthTest===========");
				OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
        System.out.println("authenticaion : " + oauth2User.getAttributes());
				System.out.println("oauth2User : " + oauth.getAttributes());
        return "세션 정보 확인3";
    }

 

 

6. 스프링 시큐리티 (세션)

 1) Authentication 객체만 가질 수 있다

  • 일반 로그인 → PrincipalDetails( implements UserDeatils)
  • OAuth 로그인 → OAuth2User
  • 위와 같이 진행될 시 로그인 User 처리가 불편하게 된다.
    - OAuth 유저도 PrincipalDetails로 묶으면 된다.

 

 2) PrincipalDetails 클래스에 코드를 추가한다

  •  PrincipalDetails 클래스에 OAuth2User를 추가로 implements 해 준다
  • getAttributes() 와 getName() 메서드를 오버라이드 해 준다
 @Data
    public class PrincipalDetails implements UserDetails, OAuth2User {	
    
    		...
    		@Override
    	  public Map<String, Object> getAttributes() {
    	      return null;
    	  }
    	
    	  @Override
    	  public String getName() {
    	      return null;
    	  }
    }

 3) 애플리케이션 재실행 후 localhost:8080/login 접속 후 구글 로그인 출력화면이다

 

 

7. PrincipalDetails

 1) PrincipalDetails

  • UserDetails를 구현하는 객체로 사용되고 있다

 2) 스프링 시큐리티 세션

  • 시프링 시큐리티 세션 정보는 단 1가지 타입인 Authentication 객체만 가지고 있을 수 있다

 3) Authentication 객체를 담는 2가지 필드

  • OAuth2User 와 UserDetails가 있다
    - 일반적으로 회원가입을 하면 UserDetails를 통해 처리를 하게 된다
    - OAuth2로 회원가입 및 로그인을 하면 OAuth2User를 통해 처리를 하게 된다
  • 여기서 문제가 발생한다
    - OAuth2User와 UserDetails에는 Member 객체를 포함하고 있지 않다

  4) 해결 방법

  • UserDetails를 PrincipalDetails에 implements 한 후에 Member 객체를 담게 한다
    - Authentication 객체를 갖는 PrincipalDetails가 만들어진다
    - PrincipalDetails 객체에는 Member가 포함되어 있다
  • OAuth2User 또한 같은 문제점을 가지고 있고 일반 회원가입 & 로그인 처리와 OAuth2 처리가 별도로 되어 문제가 발생한다
    - PrincipalDetails implements UserDetails, OAuth2User를 통해 문제를 해결한다

 

8. 회원가입

 1) PrincipalDetails 클래스에 코드를 추가 및 수정한다

@Data
public class PrincipalDetails implements UserDetails, OAuth2User {

    private Member member;
    private Map<String, Object> attributes; // 추가

		// 일반 로그인
		public PrincipalDetails(Member member) {
        this.member = member;
    }

		// 추가 & OAuth 로그인
    public PrincipalDetails(Member member, Map<String, Object> attributes) {
        this.member = member;
        this.attributes = attributes;
    }
		...

		@Override
    public Map<String, Object> getAttributes() {
        return attributes; // 수정
    }
}

  • Member 클래스에 코드를 추가한다
@Entity
@Data
@NoArgsConstructor // 추가
public class Member {
	
		@Builder // 추가
    public Member(String username, String email, String role, String provider, String providerId) {
        this.username = username;
        this.email = email;
        this.role = role;
        this.provider = provider;
        this.providerId = providerId;
    }
		
		...
}

  • 후처리 작업이 있는 PrincipalOauth2UserService 클래스의 코드를 수정한다
    - memberEntity
      : null 값일 때는 oauth로 처음 로그인 한 것이므로 회원가입 처리를 해 준다
      : null이 아닌 경우에는 기존에 1번이라도 로그인 한 이력이 있기 때문에 별도 처리를 하지 않는다
package com.memberlogin.loginjoin.config.oauth;

import com.memberlogin.loginjoin.model.Member;
import com.memberlogin.loginjoin.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    //코드 추가
    @Autowired
    private MemberRepository memberRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        //코드 추가
        OAuth2User oauth2User = super.loadUser(userRequest);

        String provider = userRequest.getClientRegistration().getClientId();
        String providerId = oauth2User.getAttribute("sub");
        String username = oauth2User.getAttribute("name");
        String email = oauth2User.getAttribute("email");
        String role = "ROSE_USER";

        Member memberEntity = memberRepository.findByUsername(username);

        if(memberEntity == null) {
            // OAuth로 처음 로그인한 유저 - 회원가입 처리
            memberEntity = Member.builder()
                    .username(username)
                    .email(email)
                    .role(role)
                    .provider(provider)
                    .providerId(providerId)
                    .build();
            memberRepository.save(memberEntity);
        }

        return new PrincipalDetails(memberEntity, oauth2User.getAttributes());
    }
//        System.out.println("userRequest : " + userRequest);
//        return super.loadUser(userRequest);
//    }
}

 

 2) 로그인하여 Member 객체 정보 확인

  • IndexController 클래스에서 /user url을 수정한다
    - 일반 회원가입 & 로그인 → /user url 접속
    - 로그아웃 후 구글 로그인 → /user url 접속
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
        System.out.println(principalDetails.getMember());
        return "user";
    }

  • 일반 회원가입 & 로그인 → /user url 접속

  • 로그아웃 후 구글 로그인 → /user url 접속

  • 일반 회원가입과 OAuth2 로그인으로 회원가입 처리가 정상적으로 되는 것을 확인할 수 있다

 

9. Index 페이지 통한 로그인 정보 확인

  • 지금까지는 로그인이 정상적으로 되었는지 확인하려면 /user (권한이 있는 경우에만 접근 가능)로 접속해서 확인했다
  • 지금부터는 Index 페이지(/)에서 로그인 되었는지 확인할 수 있도록 코드를 수정, 추가해 본다

 1) IndexController 클래스의 코드를 수정 한다

@GetMapping("/")
    public String index(@AuthenticationPrincipal PrincipalDetails principalDetails, Model model) {

        try {
            if(principalDetails.getUsername() != null) {
                model.addAttribute("username", principalDetails.getUsername());
            }
        } catch (NullPointerException e) {}
        return "index";
    }

 2) src > main > resources > templates 패키지에 index.html 파일을 생성한다

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Index Page 입니다.</title>
</head>
<body>
<h1>Index 페이지 입니다.</h1>
{{#username}}
    <h1>{{username}} 사용자입니다.</h1>
    <a href="/user">유저</a>
    <a href="/logout">로그아웃</a>
{{/username}}
{{^username}}
    <h3>로그인되지 않았습니다.</h3>
    <a href="/login">로그인 페이지로 이동</a>
    <a href="/join">회원가입 페이지로 이동</a>
{{/username}}
</body>
</html>

 3) 프로젝트를 재실행 후 애플리케이션에 접속하면 화면 출력이 변경되었음을 확인할 수 있다

로그인하지 않았을 때 로그인 했을 때

 

1. Git 설정

로컬 레포지토리와 연결할 유저 정보를 설정합니다.

버전 히스토리를 식별할 때 사용할 이름을 설정한다
git config --global user.name "[firstname lastname]"
각 기록과 연결할 이메일 주소를 설정한다
git config --global user.email “[valid-email]”

 

2. 도움말 보기

  • help 명령어를 이용하여 각 명령어 및 옵셥의 기능에 대해 살펴볼 수 있다
git에서 제공하는 모든 명령어를 볼 수 있다
git help -all
특정 command에서 사용할 수 있는 모든 옵션을 볼 수 있다
git [command] -help

 

These are common Git commands used in various situations: 다양한 상황에서 사용되는 일반적인 Git 명령은 다음과 같다
start a working area (see also: git help tutorial)

   clone Clone a repository into a new directory
   init Create an empty Git repository or reinitialize an existing one    


work on the current change (see also: git help everyday)

   add       Add file contents to the index
   mv        Move or rename a file, a directory, or a symlink
   restore   Restore working tree files
   rm        Remove files from the working tree and from the index


examine the history and state (see also: git help revisions)
   bisect    Use binary search to find the commit that introduced a bug        

   diff      Show changes between commits, commit and working tree, etc        
   grep      Print lines matching a pattern
   log       Show commit logs
   show      Show various types of objects
   status    Show the working tree status


grow, mark and tweak your common history

   branch    List, create, or delete branches
   commit    Record changes to the repository
   merge     Join two or more development histories together
   rebase    Reapply commits on top of another base tip
   reset     Reset current HEAD to the specified state
   switch    Switch branches
   tag       Create, list, delete or verify a tag object signed with GPG       




collaborate (see also: git help workflows)

   fetch     Download objects and refs from another repository
   pull      Fetch from and integrate with another repository or a local branch
   push      Update remote refs along with associated objects
작업 영역을 시작합니다(참조: git help tutorial 참조).

clone   새 디렉터리에 리포지토리 복제
init       빈 Git 저장소를 만들거나 기존 Git 저장소를 다시 초기화합니다.

현재 변경 작업(매일 도움말 보기)

add       추가 인덱스에 파일 내용 추가
mv        파일, 디렉터리 또는 심볼 링크 이동 또는 이름 변경
restore  복원작업 트리 파일 복원
rm         작업 트리와 인덱스에서 파일 제거


내역 및 상태 검사(Git help revices(도움말 수정) 참조)
이등분 버그를 발생시킨 커밋을 찾기 위해 이진 검색을 사용합니다.

diff        커밋, 커밋 및 작업 트리 간 변경 사항 표시 등
grep      패턴과 일치하는 선 인쇄
log        커밋 로그 표시
show     다양한 개체 유형 표시
status    작업 트리 상태 표시

여러분의 공통의 역사를 성장시키고, 표시하고, 수정하세요.

branch      분기 지점 목록, 생성 또는 삭제
commit     저장소에 변경 사항 기록 커밋
merge       두 개 이상의 개발 기록을 함께 결합
rebase      다른 기본 팁 위에 커밋 다시 적용
reset         현재 HEAD를 지정된 상태로 재설정합니다.
switch       스위치 분기 전환
tag            GPG로 서명된 태그 개체 생성, 나열, 삭제 또는 확인


공동 작업(참조: git 도움말 워크플로우)

fetch     다른 리포지토리에서 다운로드 개체 및 참조 가져오기
pull       다른 리포지토리 또는 로컬 분기에서 가져오기 및 통합
push     관련 개체와 함께 원격 참조 업데이트 푸시

 

3. 세팅 및 초기화

  • 레포지토리를 초가화하거나 존재하는 레포지토리를 클론할 수 있다
현재 디렉토리를 기준으로 Git 저장소가 생성된다
git init
URL을 통해 리모트 레포지토리를 로컬 레포지토리에 복제한다
git clone [url]

 

4. Stage & Commit

  • 스테이지 영역을 이용하여 커밋할 수 있다
다음 커밋을 위해 현재 디렉토리에서 수정된 파일을 확인한다
git status
다음 커밋을 위해 파일을 추가(스테이지)한다 (stage)
git add [file]
추가한 파일을 언스테이징하고 변경 사항은 유지한다
git reset [file]
스테이지되지 않은 변경 사항을 보여준다
git diff
스테이지했지만 커밋하지 않은 변경 사항을 보여준다
git diff --staged
스테이지된 컨텐츠를 메시지와 함께 커밋한다 (스냅샷 생성)
git commit -m “[descriptive message]”

 

5. Branch & Merge

  • 작업 내역을 브랜치로 분리해 컨텍스트를 변경, 통합한다
브랜치 목록을 보여준다 * 표시로 현재 작업중인 브랜치를 확인할 수 있다
git branch
현재 커밋에서 새로운 브랜치를 생성한다
git branch [branch-name]
다른 브랜치로 전환한다
git switch [branch-name]
git checkout [branch-name]

새로은 브랜치를 생성하고 해당 브랜치로 전환한다
git switch -c [branch-name]
git checkout -b [branch-name]
현재 브랜치에 특정 브랜치의 히스토리를 병합한다
git merge [branch-name]
현재 브랜치의 모든 커밋 히스토리를 보여준다
git log

 

6. 비교 및 검사

  • 로그 및 변경 사항을 검사할 수 있다
브랜치B에 없는 브랜치A의 모든 커밋 히스토리를 보여준다
git log branchB..branchA
해당 파일의 변경 사항이 담긴 모든 커밋을 표시한다 (파일 이름 변경도 표시)
git log --follow [file]
브랜치A에 있지만 브랜치B에 없는 것의 변경 내용(diff)을 표시한다 (branch간 상태 비교)
git diff branchB...branchA

 

7. 공유 및 업데이트

  • 특정 레포지토리의 업데이트 사항을 검색하여 로컬 레포지토리를 업데이트할 수 있다
url을 통해 특정 리모트 레포지토리를 별칭으로 추가한다
git remote add [alias] [url]
별칭으로 추가한 리모트 레포지토리에 있는 모든 브랜치 및 데이터를 로컬로 가져온다
git fetch [alias]
리모트 브랜치를 현재 작업중인 브랜치와 병합하여 최신 상태로 만들 수 있다
git merge [alias]/[branch]
로컬 브랜치의 커밋을 리모트 브랜치로 전송한다
git push [alias] [branch]
리모트 레포지토리의 정보를 가져와 자동으로 로컬 브랜치에 병합한다
git pull

 

8. 히스토리 수정

  • 브랜치 또는 커밋을 수정하거나 커밋 히스토리를 지울 수 있다
특정 브랜치의 분기 이후 커밋을 현재 작업중인 브랜치에 반영한다
git rebase [branch]
특정 커밋 전으로 돌아가며 스테이지된 변경 사항을 모두 삭제한다
git reset --hard [commitish]

 

9. 임시 저장

  • 브랜치를 전환하기 위해 변경되었거나 추적중인 파일을 임시로 저장할 수 있다
수정하거나 스테이지된 변경사항을 스택에 임시 저장하고 현재 작업 내역에서 삭제한다
git stash
스택에 임시 저장된 변경사항의 목록을 보여준다
git stash list
스택에 임시 저장된 변경사항을 다시 현재 작업 내역에 적용한다
git stash apply
스택에 임시 저장된 변경사항을 다시 현재 작업 내역에 적용하고 스택에서 삭제한다
git stash pop
스택에 임시 저장된 변경사항을 삭제한다
git stash drop
 

'Git' 카테고리의 다른 글

GitHub - Git branch  (0) 2022.08.22
GitHub  (0) 2022.07.10
Git(global information tracker)  (0) 2022.07.10

1. OAuth2 인증

  • 클라이언트 - 서버 - 제 3의 서비스 사이의 인증 방식이다

OAuth 인증 단계

 1) OAuth 용어

  • Resource Owner : 액세스 중인 리소스의 유저를 말한다
  • Client : Resource Owner 대신 보호된 리소스에 액세스하는 응용프로그램이다 (instagram)
  • Resource server : Client의 요청을 수락하고 응답할 수 있는 서버를 말한다 (kakao)
  • Authorization server : Resource server가 토큰을 발급받는 서버를 말한다 (kakao)
  • Authorization grant : Client가 Access token을 발급받기 위하여 사용하는 자격 증명이며, 4가지 종류가 주로 사용된다
    - Authorization Code Grant Type
    - Client Credentials Grant type
    - Implicit Grant Type
    - Resource Owner Credentials Grant Type
  • Authorization code : Access token을 발급받기 전에 필요한 code이다
    - Client ID로 이 Code를 받아온 후, Client Secret과 Code를 이용해 액세스 토큰을 받아올 수 있다
  • Access token : 보호된 리소스에 액세스하기 위하여 사용되는 credentials(자격 증명)이다
  • Scope : 주어진 Access token으로 액세스할 수 있는 리소스의 범위이다

 

2. OAuth2 동작 방식

 1) 권한 부여 방식에 따른 프로토콜 인증방식

  • Authorization Code Grant Type (권한 부여 승인 코드 방식)
    - 권한 부여 승인을 위해 자체 생성한 Authorization Code를 전달하는 방식이다
    - 가장 많이 사용되는 기본 방식이다
    - 리프레시 토큰이 사용 가능하다
    - 권한 부여 승인 요청시 응답 타입(response_type)을 code로 지정하여 요청한다

Authorization Code Grant Type

 

 

  • Client Credentials Grant : 클라이언트 자격 증명 승인 방식
    - 클라이언트 자신이 관리하는 리소스 혹은 권한 서버에 해당 클라이언트를 위한 제한된 리소스 접근 권한이 설정되어 있는 경우 사용이 가능하다
    - 자격 증명을 안전하게 보관할 수 있는 클라이언트에서만 사용되어야 한다
    - 리프레시 토큰의 사용은 불가능하다

Client Credentials Grant

 

  • Implicit Grant : 암묵적 승인 방식
    - 별도의 권한 부여 승인 코드 없이 바로 액세스 토큰을 발급하는 방식이다
    - 자격증명을 안전하게 저장하기 힘든 클라이언트(자바스크립트 등 스크립트 언어를 사용하는 브라우저)에게 최적화된 방식이다
    - 리프레시 토큰 사용이 불가능하다
    - Authorization Server는 client secret을 통해 클라이언트 인증과정을 생략한다
    - 권한 부여 승인 요청시 응답 타입(response_type)을 token으로 지정하여 요청한다

Implicit Grant

  • Resource Owner Password Credential Grant : 자원 소유자 자격 증명 승인 방식
    - 간단하게 로그인시 필요한 정보(username, password)로 액세스 토큰을 발급받는 방식이다
    - 자신의 서비스에서 제공하는 애플리케이션의 경우에만 사용되는 인증 방식이다
    - 리프레시 토큰의 사용도 가능하다
    - 예를 들어 네이버 계정으로 네이버 웹툰 애플리케이션에 로그인, 카카오 계정으로 카카오 지도 애플리케이션에 로그인하는 경우가 Resource Owner Password Credential Grant에 해당한다
    - 권한 서버, 리소스 서버, 클라이언트가 모두 같은 시스템에 속해 있을 때만 사용이 가능하다

 

 

3. OAuth2 실습

 1) Google API Console

  • 구글 Api Console로 이동한다
    - https://console.cloud.google.com/projectselector2/apis/dashboard?supportedpurview=project

 

  • 새로운 프로젝트를 생성한다
    - 이름은 자유롭게 설정한다
    - 생성한 프로젝트는 만들어지기까지 1분 내외의 시간을 기다려야 한다

  • '사용 설정된 API 및 서비스' 상단에 '프로젝트 선택'을 클릭한다

  • 생성된 프로젝트로 선택 시 API 및 서비스가 나타난다

 

  • 왼쪽 목록에서 OAuth 동의 화면을 클릭한다
    - OAuth 동의 화면에서 User Type은 외부로 한다
    - 앱 등록 수정 페이지에서 앱 이름을 작성한다
    - 본인 이메일을 선택하고 앱 로고를 등록한다
    - 저장 후 계속을 눌러 진행한다
    - 앱 도메인 등 나머지는 별도 설정 해줄 것이 현재 없어서 저장 후 계속을 눌러서 진행한다

  • OAuth 동의 화면이란 무엇인가요?
    - 승인에 OAuth 2.0을 사용하면 앱에서 Google 계정의 여러 액세스 범위에 대한 승인을 요청합니다.
    - Google에서 사용자에게 프로젝트 및 정책 요약과 요청된 액세스 범위가 포함된 동의 화면을 표시합니다.
  • OAuth 동의 범위란 무엇인가요?
    - 범위는 동의 화면에서 사용자를 대신해 액세스 권한을 요청할 사용자 데이터 종류를 정의합니다.
    - 사용 가능한 범위의 전체 목록을 참조하세요.
  • 민감한 범위란 무엇인가요?
    - 민감한 범위는 동의 화면에서 사용자에게 표시되기 전에 Google이 인증을 요청합니다.

  • 저장 후 계속을 클릭하면 '개발자 연락처 정보를 입력하라고 메시지가 표시된다
    - 필수 사항이므로 작성 후 다시 저장 후 계속을 클릭한다

  • 별도의 문제점이 없으면 다시 한번 저장 후 계속을 클릭한다

  • 최종적으로 이상이 없으면 대시보드로 돌아간다

  • 왼쪽 목록에서 사용자 인증 정보를 클릭한다
    - CREATE CREDENTIALS 클릭 후 OAuth 클라이언트 ID를 클릭하여 사용자 인증 정보를 만든다

      - 애플리케이션 유형은 웹 애플리케이션을 선택한다

      - 이름을 입력한다

      - 승인된 리디렉션 URL에 http://localhost:8080/login/oauth2/code/google 를 입력한다

      - 만들기를 클릭한다

  • 구글 로그인이 완료되면 구글 서버 → 애플리케이션 서버로 인증되었다는 코드를 보내준다
    - 코드를 통해서 액세스 토큰을 요청한다
    - 액세스 토큰을 받아 사용자 대신 서버에서 인증 처리를 할 수 있다.

 

  • 만들기 클릭 시 OAuth 클라이언트 ID와 보안 비밀번호가 발급된다
    - 해당 정보를 다른 사람에게 노출/공유해서는 안된다
    - 반드시 정보를 별도로 저장/기록 해 둔다

OAuth 클라이언트 ID/Password 생성
Json 파일

 

  • OAuth 2.0 클라이언트 ID가 생성되었다

1. Filter

  • 스프링 시큐리티는 Servlet Filter를 기반으로 서블릿을 지원한다
  • Filter 는 HTTP 요청과 응답을 변경할 수 있는 재사용 가능한 코드이다
  • 필터는 사용 여부를 지정할 수 있다
    - spring 내부에 보유하고 있는 필터를 자동으로 사용할 수 있다
    - spring 내부에 보유하고 있지만 자동으로 사용하지 않는 필터를 사용하돌록 설정할 수 있다
    - spring 내부에 보유하고 있지 않은 필터를 지정하여 사용할 수 있다
  • @을 사용하거나 .xml 을 사용하여 설정할 수 있다

클라이언트와 서버간 Filter 구조
Spring  Application Server Flow

 

2. FilterChain

  • 여러개의 Filter가 사슬처럼 연결되어 상호 동작하는 것을 의미한다

FilterChain 구조

  • 클라이언트가 앱에 요청을 보내고 컨테이너는 요청 URI의 경로를 기반으로 필터와 서블릿을 적용할지 결정한다
  • 하나의 서블릿은 단일 요청을 처리하지만, 필터는 체인을 형성하여 순서를 지정하며 실제로 요청 자체를 처리하려는 경우 필터가 나머지 체인을 거부 할 수 있다
  • 필터는 다운스트림 필터와 서블릿을 사용해서 요청과 응답을 수정할 수도 있다
  • 필터 체인의 순서는 매우 중요하며 Spring Boot는 두 가지 메커니즘을 통해 이를 관리한다
    - Filter 타입의 @Beans에 @Order를 붙이거나 Orderd를 구현한다
    - API의 일부로 순서를 가지는 FilterRegistrationBean의 일부가 된다
  • 클라이언트는 애플리케이션으로 요청을 전송하고, 컨테이너는 Servlet과 여러 Filter로 구성된 FilterChain을 만들어 요청 URI path 기반으로 HttpServletRequest를 처리한다
  • Filter는 요청이 DispatcherServlet에 의해 다뤄지기 전,후에 동작한다
  • Filter는 FilterChain을 통해 여러 필터가 연쇄적으로(순서) 동작하게 할 수 있다
  • 1개의 Servlet이 HttpServletRequest와 HttpServletResponse를 처리한다
    - Filter는 여러 개를 사용할 수 있다
    - 다운스트림의 Servlet과 Filter의 실행을 막는 경우에는 Filter에서 HttpServletResponse를 작성한다
    - 다운스트림에 있는 Servlet과 여러 Filter로 HttpServletRequest나 HttpServletResponse를 수정한다
  • Filter는 FilterChain 안에 있을 때 효력이 있다

 

3. Filter 인터페이스

  • public void init(FilterConfig filterConfig) throws ServletException
    - 필터를 웹 콘테이너 내에 생성한 후 초기화할 때 호출한다
  • public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
    - 체인을 따라 다음에 존재하는 필터로 이동한다
    - 체인의 가장 마지막에는 클라이언트가 요청한 최종 resource가 위치한다
  • public void destroy( )
    - 필터가 웹 콘테이너에서 삭제될 때 호출된다
  • 메소드에서 필터의 역할을 하는 메소드가 doFilter() 메소드이다
  • 서블릿 콘테이너는 사용자가 특정 resource를 요청할 경우, resource 사이에 필터가 있으면 필터 객체의 doFilter() 메소드를 호출하며, 호출 시점부터 필터가 작용하기 시작한다

 1) FilterChain에 사용되는 Filter 구현 코드

public class FirstFilter implements Filter {
  
     public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 작업
     }
     
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws IOException, ServletException {
        // 1. request 파리미터를 이용하여 요청의 필터 작업 수행
        // 2. 체인의 다음 필터 처리
        
        chain.doFilter(request, response); 
        // 3. response를 이용하여 응답의 필터링 작업 수행
     }
     
     public void destroy() {
        // 주로 필터가 사용한 자원을 반납
     }
  }

 

4. Filter 실습

 1) 'Spring Security 환경구성 - https://coding-mid-life.tistory.com/71 '에서 작성한 프로젝트를 기본으로 한다

  • Filter 패키지를 만들고 FirstFilter 클래스를 생성한다
    - Filter 인터페이스를 FirstFilter로 구현한다
package com.memberlogin.loginjoin.filter;

import javax.servlet.*;
import java.io.IOException;

public class FirstFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("FirstFilter 생성됨");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("========First 필터 시작========");
        chain.doFilter(request, response);
        System.out.println("========First 필터 종료========");
    }

    @Override
    public void destroy() {
        System.out.println("FirstFilter 사라짐");
        Filter.super.destroy();
    }
}

 

  • FirstFilter를 적용하기 위한 Config 파일을 작성한다
    - 프로젝트를 재실행하면 init 메서드가 실행되면서 콘솔에 'FirstFilter 생성됨'이 출력된다
    - Controller에 적용된 url로 접속하게 되면 doFilter → controller 동작 → destroy 메서드가 실행되면서  '========First 필터 시작========', '========First 필터 종료========'가 출력된다
package com.memberlogin.loginjoin.filter;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegister()  {
        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
        return registrationBean;
    }
}

  • Filter를 적용시키기 위해 다양한 옵션(addUrlPatterns, setOrder 메서드)들이 있다
    - registrationBean.addUrlPatterns(”/users/*”);
    - registrationBean.setOrder(1);

 

  • 2개 이상 필터를 적용하기 위하여 SeconfFilter 클래스를 생성한다
package com.memberlogin.loginjoin.filter;

import javax.servlet.*;
import java.io.IOException;

public class SecondFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("SecondFilter가 생성되었습니다.");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("==========Second 필터 시작==========");
        chain.doFilter(request, response);
        System.out.println("==========Second 필터 종료==========");
    }

    @Override
    public void destroy() {
        System.out.println("SecondFilter가 사라집니다.");
        Filter.super.destroy();
    }
}

 

  • Config 클래스의 내용을 수정한다
    - registrationBean.setOrder() 메서드를 통해 순서를 설정할 수 있다
@Configuration
public class Config {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegister()  {
        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
        
        //순서 설정 추가
        registrationBean.setOrder(1);
        
        return registrationBean;
    }

    //SecondFilter 설정 추가
    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilterRegister()  {
        FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>(new SecondFilter());
        registrationBean.setOrder(2);
        return registrationBean;
    }
}

  • 아래는 재실행 후 콘솔에 출력되는 화면이다

  • 애플리케이션에 접속하면 출력되는 콘솔 화면이다
    - Filter는 다운스트림에 있는 나머지 Filter와 Servlet에만 영향을 주기 때문에 순서가 중요하다

 

5. DelegatingFilterProxy

  • 스프링 시큐리티가 모든 애플리케이션의 요청에 보안이 적용되게 하는 서블릿필터이다
  • 스프링 프레임워크 기반의 웹 애플리케이션에서 서블릿 필터 라이프 사이클과 연계해 스프링 빈 의존성을 서블릿 필터에 바인딩하는데 사용한다
  • 스프링 부트는 DelegatingFilterProxy라는 Filter 구현체로 서블릿 컨테이너의 생명주기와 스프링 ApplicationContext를 연결한다
  • 서블릿 컨테이너는 자체 표준을 사용해서 Filter를 등록할 수 있지만 스프링이 정의하는 Bean은 인식하지 못한다
  • DelegatingFilterProxy는 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있지만, 모든 처리를 Filter를 구현한 스프링 빈으로 위임해 준다

  • DelegatingFilterProxy는 ApplicationContext에서 Bean Filter A를 찾아 실행한다
  • Bean Filter A는 FilterChainProxy가 된다

 

6. FilterChainProxy

  • 스프링 시큐리티는 FilterChainProxy 로 서블릿을 지원한다
  • FilterChainProxy 는 스프링 시큐리티가 제공하는 특별한 Filter로 SecurityFilterChain을 통해 여러 Filter 인스턴스로 위임할 수 있다
  • FilterChainProxy는 빈이기 때문에 보통 DelegatingFilterProxy로 감싸져 있다
  • DelegatingFilterProxy는 서블릿 필터이며, Spring IOC 컨테이너가 관리하는 Filter Bean을 갖고 있다
  • Filter Bean은 FilterChainProxy이며 객체안에서 Security와 관련된 일들이 진행된다 

  • 순수한 Servlet Filter는 본래 Spring Container 외부에 존재한다
  • DelegatingFilterProxy 클래스는 Filter를 Spring Bean으로 사용할 수 있도록 한다
  • DelegatingFilterProxy 클래스(Filter Class)는 Servlet Filter 사이에 존재하고 Spring Bean으로 등록된 Filter에게 처리를 위임한다

 

※ 참조 1

  • FilterChain에서 여러 Filter를 적용할 수 있다
  • Filter 실행 중에 DelegatingFilterProxy가 존재할 수 있다
    - DelegatingFilterProxy 내부에 있는 Bean Filter는 FilterChainProxy가 된다
  • SecurityFilterChain은 FilterChainProxy로 등록된다
  • 여러 개의 SecurityFilterChain이 있을 때 어떤 것을 사용할지는 FilterChainProxy가 결정한다
    - 가장 먼저 매칭한 SecurityFilterChain을 실행한다
    - /api/message/ url 요청
      : SecurityFilterChainn(/)도 일치하지만 SecurityFilterChain0(/api/) 패턴과 제일 먼저 매칭되므로 가장 먼저 매칭되는 SecurityFilterChain0만 실행한다
    - /message/ url 요청
      : 가지고 있는 SecurityFilterChain들을 시도해보지만 매칭되는 SecurityFilterChain 인스턴스가 없다면 SecurityFilterChainn(/)을 실행한다
  • Proxy는 “우회” “대신” 등을 의미하고 소프트웨어 디자인 패턴에서의 프록시 패턴에서 사용하는 의미와 비슷하며 네트워크에서의 프록시 개념과도 일맥상통 한다

※ 참조 2

■ Spring Security Filter 종류

  • Security Filter들은 항상 모든 Filter가 수행되지 않고 프로젝트 구성 및 설정에 따라 일부 Filter만 수행된다
  • 직접적으로 개발자가 핸들링할 필요가 없다
  • 예외적으로 개발자가 Custom Filter를 작성하고 등록할때 기존 필터들 사이에서 수행되어야 할 필요가 있는 경우가 있을때에 참고하여 적용한다
  • 각 Filter의 호출 순서는 위에서부터 아래로 진행된다
    ChannelProcessingFilter
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CorsFilter
    CsrfFilter
    LogoutFilter
    OAuth2AuthorizationRequestRedirectFilter
    Saml2WebSsoAuthenticationRequestFilter
    X509AuthenticationFilter
    AbstractPreAuthenticatedProcessingFilter
    CasAuthenticationFilter
    OAuth2LoginAuthenticationFilter
    Saml2WebSsoAuthenticationFilter
    UsernamePasswordAuthenticationFilter
    OpenIDAuthenticationFilter
    DefaultLoginPageGeneratingFilter
    DefaultLogoutPageGeneratingFilter
    ConcurrentSessionFilter
    DigestAuthenticationFilter
    BearerTokenAuthenticationFilter
    BasicAuthenticationFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    JaasApiIntegrationFilter
    RememberMeAuthenticationFilter
    AnonymousAuthenticationFilter
    OAuth2AuthorizationCodeGrantFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
    SwitchUserFilter

1. press ctrl + Q to see the documentation for the symbol at the caret

  • 해당 명령문의 구조 및 관련 문서를 볼 수 있다

2. pressto ctri + shift + I see the defonition for the symbol at the caret

  • 해당 명령문의 지정 형식을 볼 수 있다

 

1. JWT 로그인 환경 설정

  • 이전에 작성한 token 프로젝트에 로그인 관련 코드를 추가한다

 1) 로그인

  • oauth 패키지를 만들고 PrincipalDetails 클래스를 생성한다
    - UserDetails 인터페이스를 implements 한다
    - Member 객체와 생성자를 추가한다
package com.json.token.oauth;

import com.json.token.model.Member;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@Data
public class PrincipalDetails implements UserDetails {

    private Member member;

    public PrincipalDetails(Member member) {
        this.member = member;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        member.getRoleList().forEach(n -> {
            authorities.add(() -> n);
        });
        return authorities;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

  • repository 패키지를 만들고 인터페이스 MemberRepository 클래스를 생성한다
    - username을 기준으로 검색할 수 있도록 findByUsername()메서드를 생성한다
package com.json.token.repository;

import com.json.token.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
    public Member findByUsername(String member);
}

 

  • oauth 패키지에 PrincipalDetailsService 클래스를 생성한다
package com.json.token.oauth;

import com.json.token.model.Member;
import com.json.token.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member memberEntity = memberRepository.findByUsername(username);
        return new PrincipalDetails(memberEntity);
    }
}

 

  • Jwt로 로그인 처리를 하기 위해 filter 패키지에 JwtAuthenticationFilter 클래스를 생성한다
package com.json.token.filter;


import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        System.out.println("login 시도");
        return super.attemptAuthentication(request, response);
    }
}

 

  • JwtAuthenticationFilter 클래스를 실행하기 위해 SecurityConfig.java를 수정한다
    - 이전에는 .addFilter(new JwtAuthenticationFilter(authenticationManager())) 메서드를 통해 쉽게 처리할 수 있었다
    - 하지만 WebSecurityConfigureAdapter가 deprecated되면서 내부에 클래스를 만들어주거나 별도의 처리가 필요하다
    - CustomDsl 내부 클래스를 만들어 .addFilter(new JwtAuthenticationFilter(authenticationManager())) 처리를 통해 해당 필터를 적용한다
	//주석처리  http.addFilterBefore(new FirstFilter(), BasicAuthenticationFilter.class); // 추가
    
    ......
    
    			.and()
                .formLogin().disable()
                .httpBasic().disable()
                .apply(new CustomDsl()) // 추가
                .and()  //추가
                .authorizeRequests()
                
                ......
                
                     return http.build();
    }

	//추가 코드
    public class CustomDsl extends AbstractHttpConfigurer<CustomDsl, HttpSecurity> {

        @Override
        public void configure(HttpSecurity builder) throws Exception {
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
            builder
                    .addFilter(corsFilter)
                    .addFilter(new JwtAuthenticationFilter(authenticationManager));
        }
    }

      

 http.addFilterBefore(new FirstFilter(), BasicAuthenticationFilter.class);
 
 필히 주석처리 한다
 안할 경우 postman에서 결과가 나오지 않는다

 

 2) 회원가입

  • 회원가입 로직 처리를 위해 RestApiController 클래스에 코드를 추가한다
@RestController
@RequiredArgsConstructor // 추가
public class RestApiController {

	// 추가
    private final MemberRepository memberRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

		...

	// 추가
    @PostMapping("/join")
    public String join(@RequestBody Member member) {
        member.setPassword(bCryptPasswordEncoder.encode(member.getPassword()));
        member.setRoles("ROLE_USER");
        memberRepository.save(member);
        return "회원 가입 완료";
    }
}

 

  •  BCryptPasswordEncoder 빈 등록을 TokenApplication 클래스에 한다
    - 코드를 작성하면 자동으로 두개의 클래스로 분리되어진다
    - BCryptPasswordEncoder 순환 참조를 막기 위해 JwtApplication에 @Bean으로 등록한다
package com.json.token;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
public class JwtApplication {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
        SpringApplication.run(JwtApplication.class, args);
    }
}

 

  • 로그인을 진행하기 위하여 postman에서 POST를 통해 회원가입 요청을 보낸다

 

 3) 회원가입 후 로그인 시도

  • 실행 후 postman으로 body에 username, password를 포함하여 /login post 요청을 합니다.

  • 지금은 username과 password 처리를 하지 않았기 때문에 오류가 발생한다
  • intelliJ 로그를 확인해보면 login 시도(jwAuthenticationFilter가 정상적으로 적용) 후에 오류가 발생하는 것을 확인할 수 있다

  • 이를 해결하기 위해 jwtAuthenticationFilter에서 정상적인 로그인 시도를 하고 JWT 토큰을 만들어서 응답해주는 작업을 진행을 해 본다

 

 4) 사용자 정보 확인

  • request 객체에 사용자 정보가 담겨져 있는데 확인해 보도록 한다
    - JwtAuthenticationFilter 클래스의 코드를 추가한다
    - request에 담긴 정보를 가져와서 출력하는 코드를 추가한다
 try {
            BufferedReader br = request.getReader();

            String input = null;
            while((input = br.readLine()) != null) {
                System.out.println(input);
            }
        } catch (IOException e) {
            e.printStackTrace();;
        }

  • 재실행 후 postman으로 /login POST 요청을 하면 login 시도 문구 다음에 입력된 값을 확인할 수 있다
    - 아래에 오류가 뜨는 것은 정상이다

  • postman으로 /login POST 요청을 할 때 Body에 사용자 정보를 raw, JSON 형태로 보내본다
    - 콘솔에 출력되는형태가 변경되었음을 확인할 수 있다

 

 5) 로그인 처리

  • JwtAuthenticationFilter 클래스의 attemptAuthentication 메서드 코드를 수정한다
    -  기존의 코드를 아래의 코드로 대체한다
    - PrincipalDetailsService의 loadUserByUsername() 메서드가 실행된 후 정상 작동된다면 authentication이 return된다
    - 로그인이 정상적으로 된다면 authentication 객체를 session에 저장한다
    - attemptAuthentication 메서드가 정상적으로 작동하게 되면 successfulAuthentication 메서드를 실행한다
      : 해당 메서드에서 JWT 토큰을 만들어서 요청한 사용자에게 JWT 토큰을 응답으로 돌려준다
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            ObjectMapper om = new ObjectMapper();
            Member member = om.readValue(request.getInputStream(), Member.class);

            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member.getUsername(), member.getPassword());

            Authentication authentication = authenticationManager.authenticate(authenticationToken);

            PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
            return authentication;
        } catch (IOException e) {
            e.printStackTrace();;
        }
        return null;
    }

      - 재실행 후 postman에서 login을 요청해도 정상 작동하지 않는다

 

 6) JWT 토큰 생성 후 전달

  • JwtAuthenticationFilter 클래스의 successfulAuthentication 메서드를 오버라이드 한다
@Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

        System.out.println("successfulAuthentication");
        PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();

        String jwtToken = JWT.create()
                .withSubject("cos jwt token")
                .withExpiresAt(new Date(System.currentTimeMillis() + (60 * 1000 * 10)))
                .withClaim("id", principalDetails.getMember().getId())
                .withClaim("username", principalDetails.getMember().getUsername())
                .sign(Algorithm.HMAC512("cos_jwt_token"));
        response.addHeader("Authorization", "Bearer " + jwtToken);
    }

  • 재실행 후 postman에서 join으로 먼저 등록하고 login을 POST로 요청한다
    - join 후 login 시도 시 Body에는 어떤 결과도 반영되지 않는다
    - 하지만, Headers에 값을 확인해보면 Authorization - Bearer 값이 들어와 있는 것을 확인할 수 있다
    - 로그인을 성공 했을 때 JWT 토큰을 생성한다
    - 클라이언트에 JWT 토큰을 응답을 통해 보낸다
    - 요청할 때마다 JWT 토큰을 가지고 요청하게 되고 서버는 JWT 토큰이 유효한지 필터를 통해 판단하기만 하면 된다

 

2. JWT 인증권한 테스트

  • Security filter 에서 권한 및 인증이 필요한 주소를 요청 시 BasicAuthenticationFilter를 반드시 진행하게 되어있다
  • 권한이나 인증이 필요하지 않을 경우 BasicAuthenticationFilter는 적용되지 않는다

 1) 인증 권한 테스트

  • 인증 권한이 필요한 url에 접속할 때 특정 필터가 적용되도록 테스트 해 본다
  • filter 패키지에 JwtAuthorizationFilter 클래스를 생성한다
    - doFilterInternal 메서드는 인증이나 권한이 필요한 주소 요청이 있을 때마다 해당 필터를 통하게 되어 있다
package com.json.token.filter;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("인증이나 권한이 필요한 주소 요청 됨.");
        super.doFilterInternal(request, response, chain);
    }
}
  • SecurityConfig 클래스에 해당 필터를 적용한다
    - 필터가 적용되면 /api/v1/user , /api/v1/admin 등 접속 권한이 필요한 url에는 모두 로그가 출력되는 것을 확인할 수 있다
 //코드 추가
                    .addFilter(new JwtAuthorizationFilter(authenticationManager));

 

 2) Token을 통한 인증 처리

  • JwtAuthorizationFilter 클래스에 코드를 추가 및 수정한다
    - memberRepository가 필요하여 생성자에 추가했으므로 SecurityConfig에도 수정이 필요하다
    - 요청에 Authorization header 값을 가져와서 토큰을 가지고 있는지 체크한다
    - 토큰이 있더라도 verify() 메서드를 통해 username이 있는지 확인하여 서비스에 등록된 유저인지 확인한다
    - username이 정상적으로 확인된다면 UsernamePasswordAuthenticationToken을 통해 authentication을 설정한다
package com.json.token.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.json.token.model.Member;
import com.json.token.oauth.PrincipalDetails;
import com.json.token.repository.MemberRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    //코드 추가
    private MemberRepository memberRepository;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, /*코드 추가*/MemberRepository memberRepository) {
        super(authenticationManager);

        //코드 추가
        this.memberRepository = memberRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("인증이나 권한이 필요한 주소 요청 됨.");

        //코드 추가
        String jwtHeader = request.getHeader("Authorization");

        if(jwtHeader == null || !jwtHeader.startsWith("Bearer")) {
            chain.doFilter(request, response);
            return;
        }

        String jwtToken = jwtHeader.replace("Bearer ", "");

        String username = JWT.require(Algorithm.HMAC512("cos_jwt_token")).build().verify(jwtToken).getClaim("username").asString();

        if (username != null) {
            Member memberEntity = memberRepository.findByUsername(username);

            PrincipalDetails principalDetails = new PrincipalDetails(memberEntity);
            Authentication authentication = new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);

            chain.doFilter(request, response);
        }

        super.doFilterInternal(request, response, chain);
    }
}

 

  • SecurityConfig 클래스를 수정한다
    - JwtAuthorizationFilter를 수정한 후 실행 시 에러가 발생하는 부분을 수정한다
private final MemberRepository memberRepository;
.......
.addFilter(new JwtAuthorizationFilter(authenticationManager, memberRepository)); // 수정

 

 3) 로그인 및 권한 테스트

  • 권한을 확인하기 위해 RestApiController 클래스에 url을 추가한다
		// 추가
    @GetMapping("/api/v1/user")
        public String user() {
            return "user";
        }
		// 추가
    @GetMapping("/api/v1/admin")
        public String admin() {
            return "admin";
        }

  • Postman에 post 요청으로 http://localhost:8080/join에 회원가입을 요청한다

  • 회원가입 정보를 /login에 post 요청으로 로그인을 시도한다
    - Body의 내용은 공백이 정상이다
    - Header에 Authorization 값이 생성되었으며 그 값을 복사한다

  • postman에서 http://localhost:8080/api/v1/user/ 등 권한이 필요한 url에 Header에 복사한 Bearer token 값을 넣고 Get 요청을 보낸다
    - 'user'라는 결과를 응답한다
    - 다른 권한 url인 admin, manager 등에서는 403 (접근 권한 없음)이 출력된다

 

1. 인증 방식

 1) Session & Cookie 인증 방식

 

  • 사용자가 로그인 요청을 보내면 사용자를 확인 후 Session ID를 발급한다
  • 발급한 ID를 이용해 다른 요청과 응답을 처리하는 방식이다
  • 쿠키 인증은 쿠키에 사용자 정보를 담아 서버로 보내게 되는데 HTTP 방식 통신을 사용하는 경우 정보가 유출되기 쉽다
  • 세션 인증은 세션 ID를 보내므로 쿠키에 비해 보안성이 높지만 서버에서 처리를 해야하기 때문에 추가적인 데이터베이스 공간이 필요하므로 점점 커지면 부담이 될 수 있다

 

 2) Token 인증 방식

 

  • 저장소의 필요 없이 로그인 시 토큰을 발급한다
  • 데이터 요청 시에 발급받은 토큰을 헤더를 통해 전달하여 응답 받는 방식이다
  • 쿠키나 세션을 이용한 인증보다 보안성이 강하고 효율적인 인증 방법이다
  • 토큰은 데이터가 인코딩 되어 있어 누구나 디코딩하여 데이터가 유출될 수 있지만 서명 필드가 헤더와 페이로드를 통해 만들어져 데이터 변조 후 재전송을 막을 수 있다
  • statelsess 서버를 만들 수 있다
  • 인증정보를 OAuth로 이용할 수 있다
  • 일반적으로 토큰은 요청 헤더의 Authorization 필드에 담겨져 보내지게 된다
요청 헤더의 Authorization 필드 구조
Authorization: <type> <credentials>

 

 

2. Bearer 인증 테스트

  • Bearer 인증은 JWT or OAuth에 대한 토큰을 사용한다
  • 기초 환경 설정 코드를 작성한 token 폴더를 오픈한다

 1) Filter 생성

  • filter 패키지를 만들고 FirstFilter 클래스를 생성한다
package com.json.token.filter;

import javax.servlet.*;
import java.io.IOException;

public class FirstFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("FirstFilter");
    }
}

 

 

  • SecurityConfig 클래스에 코드를 추가한다
    - addFilterBefore() 또는 addFilterAfter()를 사용해서 특정 필터 전/후로 적용될 수 있게 한다
    http.addFilterBefore(new FirstFilter(), BasicAuthenticationFilter.class); // 추가

 

 2) Token 적용

  • FirstFilter 클래스에 코드를 추가한다
    - HttpServletRequest
     : ServletRequest를 상속한다
     : Http 프로토콜의 request 정보를 서블릿에 전달하기 위한 목적으로 사용한다
     : Header 정보, Parameter, cookie, URI, URL 등의 정보를 읽어들이는 메서드를 가진 클래스이다
     : Body의 Stream을 읽어들이는 메서드를 가지고 있다

    - HttpServletResponse
     : ServletResponse를 상속한다
     : Servlet이 HttpServletResponse 객체에 Content Type, 응답코드, 응답 메세지 등을 담아서 전송한다

    - HttpServlerRequest, HttpServletResponse 는 http 요청을 할 때 요청 정보가 해당 객체에 있기 때문에 사용한다
package com.json.token.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class FirstFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        //System.out.println("FirstFilter");

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        res.setCharacterEncoding("UTF-8");
        if(req.getMethod().equals("POST")) {
            String headerAuth = req.getHeader("Authorization");

            if(headerAuth.equals("codestates")) {
                chain.doFilter(req, res);
            } else {
                PrintWriter writer = res.getWriter();
                writer.println("인증 실패");
            }
        }
    }
}

 

  • POST 테스트를 위해 RestApiController 클래스에 코드를 추가한다
    @PostMapping("/token")
    public String token() {
        return "<h1>token</h1>";
    }

 

3. 테스트

 1) postman 에 http://localhost:8080/token 으로 POST 요청을 한다

  • FirstFilter 클래스에 작성한 KEY : Authorization 와 VALUE : codestates 를 입력하고 전송한다
  • 'token' 값을 전송받는다

 2) postman 에 http://localhost:8080/token 으로 POST 요청을 한다

  • 임의의 KEY : Authorization 와 VALUE : kim 를 입력하고 전송한다
  • 허용하지 않은 값으로 Authorization이 올 경우에는 '인증 실패' 값을 전송받는다

 

4. 토큰 처리

  • ID, Password가 정상적으로 들어와 로그인이 완료되면 토큰을 만들어주고 응답으로 넘겨 준다
  • 이후 요청이 올 때 header에 Authorization에 있는 토큰 값만 가져와서 확인한다
  • 넘어온 토큰이 우리가 발행한 토큰이 맞는지만 검증하면 된다

 

※ 참조 링크 

▶ 필터 이해하기 : https://atin.tistory.com/590

 

[Spring Security] 필터 Filter, SecurityFilterChain 이해하기

Spring Security를 커스터마이징하기 위해서는 그리고 이해하기 위해서는 아래 필터 체인을 이해하는 것이 좋다. 아래 그림은 인터넷에 돌아다니는 Spring Security 호출 그림을 내가 다시 깔끔하게 그

atin.tistory.com

 

1. 토큰(Token)

  • 유저의 정보를 암호화한 상태로 저장한 파일이다
  • 암호화 되어 있어서 클라이언트에 저장하여도 보안이 형성될 수 있다

 1) 토큰기반 사용 목적

  • 세션기반 인증은 서버나 DB에 유저의 정보를 담는 방식이다
  • 정보를 요청할 때마다 세션을 생성해서 DB에 일치 여부를 확인한 후 정보를 전송한다
  • 이러한 불편함을 간소화 하기 위하여 토큰기반 인증을 사용한다
  • 대표적인 토큰기반 인증방식으로는 JWT(JSON Web Token)이 있다

 

2. JWT(JSON Web Token)

 1) JWT 구성 및 진행 절차

  • Json 포맷으로 사용자에 대한 속성을 저장하는 웹 토큰이다
  • Json 객체를 basse64 방식으로 인코딩하면 아래와 같이 토큰이 생성된다
    - Header, Payload, Signature 총 3개의 블록으로 구성되어 있다

JWT 구성

  • HMAC SHA256 알고리즘(암호화 방법 중 하나)을 사용한다면 Signature는 아래와 같은 방식으로 생성된다
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
  • 토큰기반의 인증 절차
    - 클라이언트가 서버에 아이디/비밀번호를 담아 로그인 요청을 보낸다
    - 서버는 아이디/비밀번호가 일치하는지 확인하고, 클라이언트에게 보낼 암호화 된 토큰을 생성한다
      : 액세스 / 리프레시 토큰을 모두 생성한다
      : 토큰에 담길 정보(payload)는 유저를 식별할 정보, 권한이 부여된 카테고리(사진, 연락처, 기타 등등)가 될 수 있다
      : 두 종류의 토큰이 같은 정보를 담을 필요는 없다
    - 토큰을 클라이언트에게 보내주면, 클라이언트는 토큰을 저장한다
      : 저장하는 위치는 Local Storage, Session Storage, Cookie 등 다양하다
    - 클라이언트가 HTTP 헤더(Authorization 헤더) 또는 쿠키에 토큰을 담아 서버에 보낸다
      : bearer authentication을 이용한다
    - 서버는 토큰을 해독하여 발급해 준 토큰이 맞으면, 클라이언트의 요청을 처리한 후 응답을 보내준다

 

 

 2) JWT 종류

  • 액세스 토큰(Acess Token)
    - 보호된 정보들(유저의 이메일, 연락처, 사진 등)에 접근할 수 있는 권한부여에 사용한다
    - 클라이언트가 처음 인증을 받게 될 때(로그인 시), 액세스 토큰과 리프레시 토큰 두 가지를 모두 받는다
    - 실제로 권한을 얻는 데 사용하는 토큰은 액세스 토큰이다
    - 액세스 토큰은 리프레시 토큰에 비해 탈취에 대비하여 짧은 유효기간을 가지고 있다
    - 유효기간 만료시에는 리프레시 토큰을 이용하여 신규 발급을 받는다
    - 신규 발급 후에는 별도의 로그인은 필요없다
  • 리프레시 토큰(Refresh Token)
    - 클라이언트가 처음 인증을 받게 될 때(로그인 시), 액세스 토큰과 리프레시 토큰 두 가지를 모두 받는다
    - 액세스 토큰에 비해 유효기간이 길다
    - 리프레시 토큰을 사용하지 않는 경우가 많다

 

 3) JWT 기반 인증의 장점

  • Statelessness & Scalability (무상태성 & 확장성)
    - 서버는 토큰의 신뢰성만 확인하면 되고 클라이언트의 정보를 저장하지 않아도 된다
    - 토큰을 헤더에 추가하는 것으로 인증절차를 완료한다
    - 하나의 토큰으로 여러 개의 서버에 사용이 가능하다
  • 안정성
    - 암호화 한 토큰을 사용하므로 안전하다
    - 함호화 키를 노출할 필요가 없다
  • 생성의 자율성
    - 토큰을 생성하는 서버가 토큰을 만들지 않아도 사용이 가능하다
    - 토큰만을 생성하는 서버나 외부 서버에서 생성한 토큰을 사용할 수 있다
  • 권한 부여에 용이
    - 토큰의 payload 안에 접근 가능한 정보의 권한을 지정할 수 있다
    - 연락처만 가능, 갤러리와 포토만 가능 등...

 

 4) JWT 기반 인증의 단점

  • Payload는 해독할 수 있다
    - Payload는 base64로 인코딩 된다
    - 토큰을 탈취하여 Payload를 해독하면 토큰 생성시 저장한 데이터를 확인할 수 있다
    - Payload에는 중요한 정보를 넣지 않도록 한다
  • 토큰의 길이가 길어지면 네트워크에 부하를 줄 수 있다
    - 토큰에 저장하는 정보의 양이 많아질 수록 토큰의 길이는 길어진다
    - 요청할 때마다 길이가 긴 토큰을 함께 전송하면 네트워크에 부하를 줄 수 있다
  • 토큰은 자동으로 삭제되지 않는다
    - JWT는 상태를 저장하지 않기 때문에 한 번 생성된 토큰은 자동으로 삭제되지 않는다
    - 토큰 만료 시간을 반드시 추가해야 한다
    - 토큰이 탈취된 경우 토큰의 기한이 만료될 때까지 대처가 불가능하므로 만료 시간을 너무 길게 설정하지 않는다
  • 토큰은 어딘가에 저장되어야 한다
    - 토큰은 클라이언트가 인증이 필요한 요청을 보낼 경우에 함께 전송할 수 있도록 저장되어 있어야 한다

 

3. JWT 인증 환경 구성

 1) 프로젝트 생성

 

 2) 환경 설정

  • Certificate > token > src 의 buiold.gradle 의 코드 구성을 확인한다
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.json'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
 
 
  • buiold.gradle에 jwt 의존성을 추가해 준다
    - gradle은 추가 작업 후에는 항상 재실행을 해 준다
implementation 'com.auth0:java-jwt:3.19.2'

 

  • Certificate > token > src > main > resources 에 H2 서버 구성을 위하여 application.yml을 추가한다
spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
 
 
  • Certificate > token > src > main > java > com.json.token  아래에 controller 패키지를 만들고 RestApiController 클래스를 생성한다
package com.json.token.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestApiController {

    @GetMapping("/home")
    public String home() {
        return "<h1>home</h1>";
    }
}

 

  • 프로젝트 실행 및 웹 접속을 테스트 한다
localhost:8080/login localhost:8080/home
  
 
 
 3) JWT - security 설정
  • Certificate > token > src > main > java > com.json.token  아래에 model 패키지와 Member.java 를 생성한다
    -
    getRoleList는 역활을 ,로 구분하여 여러개를 넣기 때문에 사용한다
    - Role 모델을 추가하여 getRoleList를 대체할 수 있다
package com.json.token.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Data
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
    private String roles; // User, MANAGER, ADMIN

    public List<String> getRoleList() {
        if(this.roles.length() > 0) {
            return Arrays.asList(this.roles.split(","));
        }
        return new ArrayList<>();
    }
}
 
 
  • Certificate > token > src > main > java > com.json.token  아래에 config 패키지를 만들고 SecurityConfig 클래스를 생성한다
    - JWT를 사용하기 위한 기본 설정
     : JWT는 headers에 Authorization 값에 토큰을 보내는 방식이다. (⇒ Bearer)
     : 토큰 정보는 노출되면 안되지만 노출되게 되더라도 유효 시간을 지정해뒀기 때문에 큰 위험이 없다 : 

    - .http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
     : Web은 기본적으로 stateless인데 seesion이나 cookie를 사용할 수 있다
     : session / cookie를 만들지 않고 STATELESS로 진행하겠다는 의미이다


    - .formLogin().disable()
     : form Login을 사용하지 않는다

    - .httpBasic().disable()
     : http 통신을 할 때 headers에 Authorization 값을 ID, Password를 입력하는 방식이다
     : https를 사용하면 ID와 Password가 암호화되어 전달된다
     : http 로그인 방식을 사용하지 않는다
package com.json.token.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin().disable()
                .httpBasic().disable()
                .authorizeRequests()
                .antMatchers("/api/v1/user/**")
                .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                .antMatchers("/api/v1/manager/**")
                .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                .antMatchers("/api/v1/admin/**")
                .access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
        return http.build();
    }
}
 
 
  •  config 패키지에 CorsConfig 글래스를 생성한다
    - .setAllowCredentials()
     : 서버가 응답할 때 json을 자바스크립트에서 처리할 수 있게 설정해 준다

    - .addAllowedOrigin(”*”)
     : 모든 ip에 응답을 허용한다

    - .addAllowedHeader(”*”)
     : 모든 header에 응답을 허용한다

    - .addAllowedMethod(”*”)
     : 모든 post, get, patch, delete 요청을 허용한다
package com.json.token.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/api/**", config);

        return new CorsFilter(source);
    }
}
 
 
  • SecurityConfig 클래스에 코드를 추가한다
@RequiredArgsConstructor //@ 추가

public class SecurityConfig {

    private final CorsFilter corsFilter; //코드 추가

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                .addFilter(corsFilter) // 추가
                
                .formLogin().disable()
 
 
  • 프로젝트를 재실행 후 웹에 접속한다
    - localhost:8080 , localhost:8080/login 등... 접속은 404 에러가 발생한다

      - localhost:8080/home 는 로그인 없이 접속된다

      - http://localhost:8080/api/v1/user, http://localhost:8080/api/v1/manager, http://localhost:8080/api/v1/admin 는 403에러가 발생한다

http://localhost:8080/api/v1/user http://localhost:8080/api/v1/manager http://localhost:8080/api/v1/admin

 

 

 

※ 참조 링크

▶ JWT : https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 Bearer token : https://learning.postman.com/docs/sending-requests/authorization/#bearer-token

 

Authorizing requests | Postman Learning Center

Authorizing requests: documentation for Postman, the collaboration platform for API development. Create better APIs—faster.

learning.postman.com

https://datatracker.ietf.org/doc/html/rfc6750

 

RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage

 

datatracker.ietf.org

 

 

1. 기본 환경 설정

 1) https://start.spring.io/ 에서 spring initializr 를 사용하여 프로젝트를 구성하고 GENERATE로 생성한다

프로젝트 설정

 2) IntelliJ를 열고 프로젝트 파일을 오픈한다

  • 생성된 파일을 지정 폴더에 압축풀기를 한다
  • 압축을 푼 폴더에서 build 파일을 intelliJ에서 오픈한다

 3) 의존성을 설정한다

  • build.gradle 파일을 열고 아래와 같이 코드를 작성한다
plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-mustache'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
	useJUnitPlatform()
}

  • gradle 에서 코드를 추가하거나 수정한 후에는 Gradle 탭에서 Reload를 해 준다

 

 3) db.jpa 설정을 추가한다

  • resource 폴더 아래에 application.yml 파일을 추가한다
spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

 

 4) 애플리케이션 출력 화면을 구성하기 위해 2개의 파일을 추가한다

  • src/resources/templates 아래에 추가한다
  • loginForm.html
    - 로그인 화면에 출력되는 HTML 코드이다
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr />
<form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" /><br />
    <input type="password" name="password" placeholder="Password" /><br />
    <button>로그인</button>
</form>
<a href="join">회원가입</a>
</body>
</html>
  • joinForm.html
    - 회원가입 화면에 출력되는 HTML 코드이다
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr />
<form action="/join" method="post">
  <input type="text" name="username" placeholder="Username" /><br />
  <input type="password" name="password" placeholder="Password" /><br />
  <input type="email" name="email" placeholder="Email" /><br />
  <button>회원가입</button>
</form>
</body>
</html>

 

2. 기본 실행

 1) 애플리케이션을 실행한다

  • intelliJ에서 프로젝트를 실행한다
  • 콘솔 화면에 아래와 같이 password가 부여되고 설명이 출력되면 기본 설정이 적용된 것이다

  • http://localhost:8080 으로 접속한다

  • Username : user / password : 콘솔창에 표시된 password 를 작성한다
  • 현재 login 화면 구성이 없기 때문에 Error 메시지가 출력될 것이다

 

3. Spring Security Configuration 적용

 1) src > main > java > com.memberlogin.loginjoin 아래에 controller 패키지와 IndexController.java를 생성한다

  • 코드 생성 후 반드시 애플리케이션을 재실행 한다
package com.memberlogin.loginjoin.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {

    @GetMapping("/")
    public @ResponseBody String index() {
        return "index";
    }

    @GetMapping("/user")
    public @ResponseBody String user() {
        return "user";
    }

    @GetMapping("/admin")
    public @ResponseBody String admin() {
        return "admin";
    }

    @GetMapping("/manager")
    public @ResponseBody String manager() {
        return "manager";
    }

    @GetMapping("/login")
    public @ResponseBody String login() {
        return "login";
    }

    @GetMapping("/join")
    public @ResponseBody String join() {
        return "join";
    }
}
  • 애플리케이션을 재실행 후 web 접속하여 로그인을 하면 아래와 같이 출력된다
    - url을 지정하지 않은 경우에 출력은 'index'로 설정되어 있다

  • 총 5개의 url을 설정하였고 5개 중 1개의 url을 제외하고는 모두 로그인 시 정상적으로 작동하는 것을 확인할 수 있다
    - /login의 경우에는 Spring Security가 처리하고 있기 때문에 작동하지 않는다
localhost:8080/login localhost:8080/user localhost:8080/admin localhost:8080/manager localhost:8080/join

 

 2) src > main > java > com.memberlogin.loginjoin 아래에 config 패키지와 SecurityConfig.java를 생성한다

  • @Configuration과 @EnableWebSecurity를 추가한다
    - @EnableWebSecurity 추가 시 스프링 시큐리티 필터가 스프링 필터체인에 등록 된다
  • filterChain 메서드를 @Bean으로 등록한 후 스프링 컨테이너에서 관리할 수 있도록 한다
  • http.csrf().disable(); 의 경우에는 form 태그로만 요청이 가능해지고 postman등의 요청이 불가능하게 된다
    - csrf를 disable 한다
  • http.headers().frameOptions().disable(); 은 h2 연결할 때 필요하다
  • Config 설정이 되면  /login에 접속이 가능하게 된다
package com.memberlogin.loginjoin.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
				http.headers().frameOptions().disable();

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
        return http.build();
    }

}
  • 애플리케이션을 재실행 후 web 접속하여 로그인을 하면 아래와 같이 출력된다
    - http://localhost:8080 으로 접속하면 index 가 표시되고
    - http://localhost:8080/login 으로 접속하면 login 이 표시된다
  • /, /login, /join 3개의 url은 로그인 없이도 접속이 가능하다

  • /admin, /manager url의 경우에는 권한이 없기 때문에 403 에러가 출력된다

admin, manager 접속 시 콘솔창에 session ID가 생성되는 것을 볼 수 있다

  • 아래 코드를 추가한 후 재실행하고 접속하면 로그인이 가능해 진다
    - admin 과 manager 로 접속 시 login이 출력된다
                .and()
                .formLogin()
                .loginPage("/login");

 

 3) config 패키지 아래에 WebMvcConfig.java를 생성한다

  • mustache → html 사용할 수 있도록 설정한다
package com.memberlogin.loginjoin.config;

import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        MustacheViewResolver resolver = new MustacheViewResolver();
        resolver.setCharset("UTF-8");
        resolver.setContentType("text/html; charset=UTF-8");
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");

        registry.viewResolver(resolver);
    }
}
  • ViewResolver 구현 클래스 종류

 4) src > main > java > com.memberlogin.loginjoin 아래에 model 패키지와 Member.java를 생성한다

package com.memberlogin.loginjoin.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Entity
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String role;
    private LocalDateTime createdAt = LocalDateTime.now();
}

 

 5) src > main > java > com.memberlogin.loginjoin 아래에 repository 패키지와 MemberRepository.java를 생성한다

package com.memberlogin.loginjoin.repository;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
}

 

4. 회원 가입

 1) 회원가입에 필요한 코드를 수정한다

  • controller 패키지의 IndexController 클래스를 수정한다
  • 코드를 수정하고 실행하면 정상적으로 db에 저장되게 된다
@Controller
public class IndexController {

    @Autowired
    MemberRepository memberRepository;

		...

    @GetMapping("/login")
    public String login() {
        return "loginForm";
    }

    @GetMapping("/join")
    public String joinForm() {
        return "joinForm";
    }

    @PostMapping("/join")
    public @ResponseBody String join(Member member) {

        member.setRole("ROLE_USER");
        memberRepository.save(member);
        return "join";
    }
}

  • 애플리케이션을 재실행하면 아래와 같이 로그인 페이지가 정상적으로 출력되고 로그인 시 index 가 출력된다

 

 2) 패스워드 암호화를 위한 코드 수정

  • config 패키지의 SecurityConfig 클래스를 수정한다
    - 아래 이미지에 @Bean 이 한 곳 빠져 있다...이 오류 때문에 고생 많이 했다...그래서 이미지를 수정하지 않고 둔다..^^
    - @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;
        -> IndexController 클래스의 Bean 충돌로 인하여 서버 실행에 에러가 발생한다.
        -> 주석 처리해 주면 해결된다
public class SecurityConfig {
		
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    
    ...

    @Bean // 추가
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
		
    ...
}

  • controller 패키지의 IndexController 클래스를 수정한다
    @PostMapping("/join")
    public String join(Member member) {
        member.setRole("ROLE_USER");
        String rawPassword = member.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        member.setPassword(encPassword);

        memberRepository.save(member);

        return "redirect:/login";
    }
  • 애플리케이션 재실행 후 웹에 접속하여 확인한다
  • http://localhost:8080/h2 에 접속하여 입력된 데이터를 확인할 수 있다

 

5. 로그인

 1) config > auth 패키지를 만들고 PrincipalDetails 클래스를 생성한다

  • PrincipalDetails 클래스에 implements UserDetails와 메서드를 오버라이드 한다
  •  org.springframework.security.core.userdetails
    - 보안 목적으로 Spring Security에서 직접 사용되지 않는다
    - 단순히 나중에 객체로 캡슐화되는 사용자 정보를 저장 Authentication 한다
    - 보안과 관련되지 않은 사용자 정보(예: 이메일 주소, 전화번호 등)를 편리한 위치에 저장할 수 있다
 

org.springframework.security.core.userdetails (spring-security-docs 5.7.2 API)

 

docs.spring.io

  • 시큐리티는 /login 주소에 요청이 오면 대신 로그인을 진행한다
  • Authentication 타입 객체이며 안에 Member 정보가 있어야 한다
  • 로그인 진행이 완료되면 security session을 만들어 준다 (Security ContextHolder)
package com.memberlogin.loginjoin.config.auth;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class PrincipalDetails implements UserDetails {

    private Member member;

    public PrincipalDetails(Member member) {
        this.member = member;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return member.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        //isCredentialsNonExpired()는 암호 사용 기간이 지났는지 여부를 확인한다
        return true;
    }

    @Override
    public boolean isEnabled() {
        //isEnabled()은 특정 사이트 규칙에 따라 return false로 설정한다
        // ex) 1년 동안 로그인을 하지 않았을 경우
        return true;
    }
}
//현재 따로 규칙이 없기 때문에 isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled 메서드들을
//모두 return true로 설정한다

 

 2) config > auth 패키지에 PrincipalDetailsService 클래스를 생성한다

  • PrincipalDetailsService에 implement UserDetailsService와 메서드를 오버라이드 한다
  • Security 설정에서 loginProcessingUrl(”/login”);으로 요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어 있는 loadUserByUsername 함수가 실행된다
  • 메서드 파라미터에 username이라고 되어있으면 form을 통해 username을 가져올 때 name이 반드시 매치되야 한다
    - 이름을 똑같이 변경해주거나
    - SecurityConfig에 .loginPage() 아래에 .usernameParameter(”다른 이름")으로 추가해야 한다
  • loadUserByUsername 함수가 Authentication으로 값이 return 된다
package com.memberlogin.loginjoin.config.auth;

import com.memberlogin.loginjoin.model.Member;
import com.memberlogin.loginjoin.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member memberEntity = memberRepository.findByUsername(username);
        System.out.println("username : " + username);
        if(memberEntity != null) {
            return new PrincipalDetails(memberEntity);
        }
        return null;
    }
}

 

 3) MemberRepository에 findByUsername 메서드를 추가해줍니다.

  • PrincipalDetailsService에 있는 loadUserByUsername에서 사용하기 때문에 해당 메서드를 추가한다
  • UserDetailService에서 findByUsername을 구현처리 할 때 자동적으로 생성되지만 한번 더 확인한다

package com.memberlogin.loginjoin.repository;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
    public Member findByUsername(String username);
}
  • 애플리케이션을 재실행 한 후 웹에 http://local:8080/uesr 접속하여 회원 가입 후 로그인을 진행한다
    - 이상없이 회원가입과 로그인이 진행된다
    - 친절하게 비밀번호에 대한 확인 창도 표시된다
  • localhost:8080/login 과 localhost:8080/join, localhost:8080/user 은 원활하게 접속이 된다
로그인을 실행한 후 화면이다
로그아웃은 하지 않았다



 


  • localhost:8080/admin 과 localhost:8080/manager는 다시 403 에러가 뜨는 것을 확인할 수 있다

 

6. 권한 처리

 1) manager, admin 처리

  • 회원가입 후 h2 database에서 ROLE을 ROLE_ADMIN으로 변경해줍니다.
  • localhost:8080/admin 과 localhost:8080/manager 에 정상적으로 접근이 가능하고 그 외 모든 url 또한 접근이 가능한 것을 확인할 수 있다
  • SecurityConfig 클래스에 코드를 추가한다
@Configuration
@EnableWebSecurity

//권한 처리 추가
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

/*@EnableGlobalMethodSecurity(securedEnabled = true)
  - Secured 애너테이션이 활성화 된다
  - SecurityConfig에서 접근 권한 설정이 아닌 Controller에서 애너테이션으로 관리할 수 있게 된다*/
  
/*@EnableGlobalMethodSecurity(prePostEnabled=true)
  - PreAuthorize, PostAuthorize 애너테이션이 활성화 된다.*/

public class SecurityConfig {

 

 2) IndexController.java에 새로운 메서드와 @Secured와 @preAuthorize를 추가한다

  //권한 추가
   @Secured("ROLE_ADMIN")
    @GetMapping("/info")
    public @ResponseBody String info() {
        return "info";
    }

    @PostMapping("/join")
    public String join(Member member) {
        member.setRole("ROLE_USER");
        String rawPassword = member.getPassword
  • SecurityConfig에 .antMatchers("/info/**").access("hasRole('ROLE_ADMIN')") 코드를 추가하는 것과 같은 동작이 된다
    - @Secured는 1개의 권한을 주고 싶을 때 사용한다
    - @PreAuthorize는 1개 이상의 권한을 주고 싶을 때 사용한다
      : #을 사용하면 파라미터에 접근할 수 있다
      : ex) @PreAuthorize("isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))")
    - @PostAuthorize는 메서드가 실행되고 응답하기 직전에 권한을 검사하는데 사용된다
    - 클라이언트에 응답하기 전에 로그인 상태 또는 반환되는 사용자 이름과 현재 사용자 이름에 대한 검사, 현재 사용자가 관리자 권한을 가지고 있는지 등의 권한 후처리를 할 수 있다
	@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    @GetMapping("/data")
    public @ResponseBody String data() {
        return "data";
    }

 

 

※ 참조 링크

BCryptPasswordEncoder :

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html

 

BCryptPasswordEncoder (spring-security-docs 5.7.2 API)

All Implemented Interfaces: PasswordEncoder public class BCryptPasswordEncoder extends java.lang.Object implements PasswordEncoder Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients can optionally supply a "version" ($2

docs.spring.io

 

'인증 & 보안' 카테고리의 다른 글

웹 보안 공격  (0) 2022.07.23
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. SQL Injection

 1) 공격 방식

  • 데이터베이스에서 임의의 SQL문을 실행할 수 있도록 명령어를 삽입하는 공격 유형이다
  • 응용 프로그램의 보안상의 허점을 이용해 데이터베이스를 비정상적으로 조작하여 기록을 삭제하거나 데이터를 유출한다

 

2) SQL Injection 대응

  • 입력(요청) 값을 검증한다
    - SQL문은 사람이 사용하는 자연어와 비슷하기 때문에 키워드를 막기에는 한계가 있다
    - 블랙리스트가 아닌 화이트리스트 방식으로 해당 키워드가 들어오면 다른 값으로 치환하여 SQL Injection에 대응할 수 있다
      : 화이트리스트란 기본 정책이 모두 차단인 상황에서 예외적으로 접근이 가능한 대상을 지정하는 방식 또는 그 지정된 대상들을 말한다
  • Prepared Statement 구문을 사용한다
    - Prepared Statement 구문을 사용하면 사용자의 입력이 SQL문으로부터 분리되어 SQL Injection을 방어할 수 있다
    - 사용자의 입력 값이 전달 되기 전에 데이터베이스가 미리 컴파일하여 SQL을 바로 실행하지 않고 대기한다
    - 사용자의 입력 값을 단순 텍스트로 인식하여 입력 값이 SQL문이 아닌 단순 텍스트로 적용되어 공격에 실패한다
  • Error Message 노출을 금지한다
    - 공격자는 데이터베이스의 Error Message를 통해 테이블이나 컬럼 등 데이터베이스의 정보를 얻을 수 있다
    - 에러가 발생한 SQL문과 에러 내용이 클라이언트에 노출되지 않도록 별도의 에러핸들링이 필요하다

 

2. Cross-Site Request Forgery (CSRF)

  • 다른 오리진(cross-site)에서 유저가 보내는 요청을 조작(forgery)하는 것이다
  • 다른 오리진이기 때문에 공격자가 직접 데이터를 접근할 수 없다

 1) 공격 조건

  • 클라이언트가 로그인 했을 경우에 쿠키로 클라이언트를 확인할 수 있어야 한다
  • 클라이언트가 요청한 parameter에 예측할 수 있는 정보가 담겨있어야 한다
  • GET 요청으로 CSRF 공격
    - http://bank.com/transfer?account_number=username&amount=1000$
    → http://bank.com/transfer?account_number=user계좌번호&amount=1000$
     http://bank.com/transfer?account_number=해커계좌번호&amount=1000$

 

 

 2) Cross-Site Request Forgery 대응

  • CSRF 토큰을 사용한다
    - 서버측에서 CSRF 공격에 보호하기 위한 문자열을 클라이언트의 브라우저와 웹 앱에만 제공한다 
  • SAME-site coolie를 사용하여 같은 도메인에서만 세션과 쿠키를 사용할 수 있도록 한다

 

 

※ 참조 링크

▶ "동일 사이트" 및 "동일 출처" 이해하기 : https://web.dev/same-site-same-origin/#same-site-cross-site

 

"동일 사이트" 및 "동일 출처" 이해하기

"동일 사이트"와 "동일 출처"는 자주 인용되지만 종종 오해됩니다. 이 기사는 이 둘이 무엇이며 어떻게 다른지 이해하는 데 도움이 됩니다.

web.dev

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. 해싱(Hashing)

  • 인증을 거치지 않고 단순하게 정보를 요청하고 응답한다면 누구나 접근이 가능하다

  • 간단한 password를 이용하여 인증을 진행하는 방식은 위험성이 매우 높다
    - DB가 타인으로부터 공격을 받아서 password가 노출된다면 클라이언트가 이용하고 있는 다른 애플리케이션까지 피해를 줄 수 있다

  • 암호화(encryption)는 일련의 정보를 임의의 방식을 사용하여 다른 형태로 변환하여, 해당 방식에 대한 정보를 소유한 사람을 제외하고 이해할 수 없도록 '알고리즘'을 이용해 정보를 관리하는 과정이다
  • 예를 들어 입력받은 문자열을 입력받은 숫자의 값 만큼 알파벳 순서를 건너뛴 문자로 변환하여 새로운 문자열을 반환하도록 한다
    - 단순하고 간단하지만 암호화에 속한다
    - 아래는 예제 코드이다
package CA.example;

public class Hashing {
    public static String shiftBy(String content, int offset) {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < content.length(); i++) {
            char shiftByOffset = (char)(content.charAt(i) + offset);
            result.append(shiftByOffset);
        }
        return result.toString();
    }

    public static void main(String[ ] args) {
        shiftBy(content:"apple", offset: 2);   //crrng 문자열 반환
        shiftBy(content:"crrng", offset: -2);  //apple 문자열 반환
    }
}

 

2. 쿠키(cookie)

  • 서버에서 클라이언트에 데이터를 저장하는 방법이다
  • 서버는 클라이언트에게서 쿠키를 이용하여 데이터를 가져올 수 있다
  • 서버가 클라이언트로부터 데이터를 가져오기 위해서는 특정 조건들을 만족해야 한다
  • 쿠키의 특성을 이용하여 서버는 클라이언트에 인증정보를 담은 쿠키를 전송한다
  • 클라이언트는 전송받은 쿠키를 이용하여 무상태성인 인터넷 연결을 안전하게 유지할 수 있다
  • 쿠키는 오랜 시간 유지될 수 있고 자바스크립트에서 접근이 가능하므로 보안에 취약하다

 1) 데이터를 가져오기 위한 쿠키 옵션

URL

  • 도메인(Domain)
    - 쿠키에서 도메인은 도메인 이름만 포함한다
    - 쿠키 옵션에서 도메인은 포트 및 서브 도메인 정보와 세부 경로를 포함하지 않는다
    - 클라이언트의 쿠키 옵션에 도메인 정보가 존재하고 도메인 옵션과 서버의 도메인이 일치하면 쿠키를 전송한다

 

  • 패스(Path)
    - Path는 세부경로이며 서버가 라우팅할 때 사용하느경로이다
    - 기본적으로 '/'으로 표시한다
    - path 옵션은 /file.html로 설정되어 있다면 file.html/user/username의 경로라도 /file.html을 만족하므로 쿠키 전송이 가능하다
  • MaxAge or Expires
    - 쿠키가 유효한 기간을 설정하는 옵션이다
    - 쿠키의 유효기간이 정해져 있지 않으면 탈취하기가 쉬워지므로 보안 측면에서 중요한 옵션이다
    - maxAge는 몇 초 동안 유효할 지를 설정하는 옵션이다
    - Expires는 클라이언트의 시간을 기준으로 유효한 날짜와 시간을 지정하는 옵션이다
      : 지정한 날자와 시간을 초과하면 쿠키는 자동으로 삭제된다
    - 세션 쿠키는 MaxAge나 Expires 옵션과 상관없이 크라우져가 실행 중에만 사용할 수 있는 쿠키이며, 브라우져가 종료되면 자동으로 삭제되는 쿠키이다
    - 영속성 쿠키는 브라우져의 종료 여부와 관계없이 MaxAge와 Expires 옵션에 지정된 내용만큼 사용 가능한 쿠키이다
  • Secure
    - 쿠키를 전송할 때 프로토콜 옵션에 따라 전송 여부를 결정한다
    - true로 옵션이 설정되어 있으면 HTTPS 포로토콜을 이용하는 경우에만 쿠키를 전송할 수 있다
    - 옵션 설정이 없다면 HTTP와 HTTPS 모두 쿠키를 전송할 수 있다
  • HttpOnly
    - 자바스크립트에서 브라우져의 쿠키에 접근을 허용할 것인지를 결정한다
    - true 옵션으로 설정되어 있으면 자바스크립트에서 쿠키에 접근이 불가하다
    - 옵션 설정이 없는 경우에 기본적으로 false 값으로 지정된다
    - false 값으로 지정되어 있으면 자바스크립트에서 쿠키에 접근이 가능하여 XSS 공격에 취약해 진다
  • SameSite
    -  Cross-Origin 요청을 받은 경우에는 사용한 메서드와 해당 옵션(get,post,put,patch,...)의 조합에 따라 서버의 쿠키 전송 여부를결정한다
    - 사용 가능한 옵션
      → Lax : Cross-Origin 요청이면 'GET'메서드에 대해서만 쿠키를 전송할 수 있다
       Strict : Cross-Origin 요청이 아닌 same-site 경우에만 쿠키를 전송할 수 있다
       None : 항상 쿠키를 전송할 수 있지만, Secure 옵션이 있는 경우에 한하여 전송한다
      
    - same-site는 요청을 보낸 Origin과 서버의 Domain, Protocol, Port가 같은 경우를 의미한다
    - 요청을 보낸 Origin과 서버의 Domain, Protocol, Port가 하나라도 다르면 Cross-Origin으로 구분한다

 2) 쿠키 전송

  • 서버에서 클라이언트로 쿠키를 처음 전송할 때에는 Header에 Set-Cookie 라는 프로퍼티에 쿠키를 담아 전송한다
  • 이후 쿠키를 전송할 경우에는 클라이언트는 Header에 Cookie라는 프로퍼티에 쿠키를 담아 서버에 전송한다

 

3. Session

  • 클라이언트가 인증에 성공한 상태를 Session이라고 한다
  • 서버는 in-memory, 세션스토어(redis 같은 트랜젝션이 빠른 DB)에 세션을 저장한다
  • 세션이 만들어지면 세션 id가 생성되고 인증된 클라이언트에게 전달된다
  • 클라이언트는 서버에서 발급한 세션 아이디를 쿠키에 저장하고 사용한다
  • 쿠키에 저장된 데이터는 보안에 취약하므로 사용이 종료되면 삭제해야 한다
  • 서버가 클라이언트의 쿠키를 임의로 삭제할 수는 없지만, set-cookie로 클라이언트에게 쿠키를 전송할 때 세션 아이디의 키값을 무효한 값으로 갱신할 수 있다

 

 

 

 

※ 참조 링크

 교차 출처 리소스 공유 (CORS) : https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

Same-origin Policy : https://en.wikipedia.org/wiki/Same-origin_policy

 

Same-origin policy - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Security measure for client-side scripting In computing, the same-origin policy (sometimes abbreviated as SOP) is an important concept in the web application security model. Under the

en.wikipedia.org

사이트 간 요청 위조 : https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0

 

사이트 간 요청 위조 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

Set-Cookie Attributes : https://urclass.codestates.com/53317456-fd52-431d-802c-28a53dc3735a?playlist=2338

 

Set-Cookie - HTTP | MDN

The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent can send it back to the server later. To send multiple cookies, multiple Set-Cookie headers should be sent in the same response.

developer.mozilla.org

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
웹 보안 공격  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. Spring Security

  • Spring Framework 기반으로 애플리케이션의 인증(Authentication)과 인가(Authorization or 권한 부여) 기능을 가진 프레임워크이다
  • 모든 필수 파일은 애플리케이션에 포함되어 있으며, 애플리케이션이 독립적으로 작동하는 것을 목표로 한다
  • Java Runtime Environment에 특별한 구성 파일을 배치할 필요가 없다
  •  JAAS(Java Authentication and Authorization Service) 정책 파일의 구성이 필요 없다
  • Spring Security를 ​​공통 클래스 경로 위치에 배치하지 않아도 된다
  • 한 시스템에서 다른 시스템으로 대상 아티팩트(JAR, WAR 또는 EAR)를 복사함과 동시에 작동하므로 최대 배포 시간의 유연성을 제공한다
  • Apache 2.0 라이센스 하에 출시된 오픈 소스 소프트웨어이다
  • Java 8 이상의 환경이 필요하다
  • Spring Security는 종속성을 Maven 아티팩트로 배포한다

 

2. Spring Security 용어

  • 주체(Principal)
    - 유저, 기기, 시스템 등이 될 수 있지만, 일반적으로 유저(사용자)를 의미한다
  • 인증(Authentication)
    - 특정 리소스에 접근하려고 하는 사용자가 누구인지 확인하는 경우에 사용한다
    - 주체의 신원(identity)을 증명하는 과정이다
    - 주체는 자신을 인증하기 위해 신원 증명 정보(credential)을 제시한다
    - 주체가 유저일 경우 신원 증명 정보는 패스워드(password)이다
  • 인가(Authorization = 권한 부여)
    - 인증을 마친 유저에게 권한(authority)을 부여하여 애플리케이션의 특정 리소스에 접근할 수 있게 허가하는 과정이다
    - 인가는 반드시 인증 과정 이후 수행되어야 한다
    - 권한은 롤 형태로 부여한다
  • 접근 통제(Access control)
    - 어떤 유저가 애플리케이션 리소스에 접근하도록 허락할지를 제어하는 행위이다
    - 접근 통제 결정(access control decision)이 필요하다
    - 리소스의 접근 속성과 유저에게 부여된 권한 또는 다른 속성들을 결정한다

 

3. Spring Security 제공 기능

  • 모든 요청에 대해서 인증을 요구한다
  • 사용자 이름과 암호를 가진 사용자가 양식 기반으로 인증할 수 있도록 허용한다
  • 사용자의 로그아웃을 허용한다
  • CSRF(Cross-Site Request Forgery) 공격을 방지한다
    - CSRF 공격은 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 의미한다
  • Session Fixsation(세션 고정 공격)을 보호 한다
    - Session Fixsation은 공격자가 자신의 세션 id를 다른 사용자에게 넘겨주고 공격하게 하는 방법을 의미한다
  • 보안 헤더 통합
    - HSTS 강화
    - X-Content-TypeOptions
    - 캐시 컨트롤(정적 리소스 캐싱)
    - X-XSS-Protection XSS 보안
     : 스크립트 공격 보안
    - 클릭재킹을 방지하는 X-Frame 옵션 통합
     : 클릭재킹이란 웹 사용자가 클릭하고 있는 것에 다른 것을 대체하여 클릭하도록 속이는 기법으로 공격자는 비밀 정보 유출이나 컴퓨터에 대한 제어권을 획득할 수 있게 된다
  • Servlet API 제공

 

 

 

 

※ 참조 링크

Spring Security Tutorial : https://docs.spring.io/spring-security/reference/index.html

 

Spring Security :: Spring Security

If you are ready to start securing an application see the Getting Started sections for servlet and reactive. These sections will walk you through creating your first Spring Security applications. If you want to understand how Spring Security works, you can

docs.spring.io

Spring Security Q&A

 : https://stackoverflow.com/questions/tagged/spring-security

 

Newest 'spring-security' Questions

Stack Overflow | The World’s Largest Online Community for Developers

stackoverflow.com

 : https://github.com/spring-projects/spring-security/issues

 

GitHub - spring-projects/spring-security: Spring Security

Spring Security. Contribute to spring-projects/spring-security development by creating an account on GitHub.

github.com

Spring Security Source Code : https://github.com/spring-projects/spring-security/

 

GitHub - spring-projects/spring-security: Spring Security

Spring Security. Contribute to spring-projects/spring-security development by creating an account on GitHub.

github.com

 HSTS(HTTP Strict Transport Security) 기능

 : https://m.blog.naver.com/PostView.nhn?blogId=aepkoreanet&logNo=221575708943&proxyReferer=https:%2F%2Fwww.google.com%2F 

 

HSTS(HTTP Strict Transport Security) 기능

HSTS(HTTP Strict Transport Security) HSTS(HTTP Strict Transport Security)는, ...

blog.naver.com

클릭재킹 : https://ko.wikipedia.org/wiki/%ED%81%B4%EB%A6%AD%EC%9E%AC%ED%82%B9

 

클릭재킹 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 클릭재킹(Clickjacking, User Interface redress attack, UI redress attack, UI redressing)은 웹 사용자가 자신이 클릭하고 있다고 인지하는 것과 다른 어떤 것을 클릭하게 속이는

ko.wikipedia.org

 

1. HTTPS (Hyper Text Transfer Protocol Secure Socket layer)

  • HTTP + Secure 의 합성어이다
  • HTTP over SSL(TLS), HTTP over Secure 라고도 한다
  • HTTP 프로토콜에 보안성이 추가된 시스템이다
  • HTTP 요청을 SSL 또는 TLS 알고리즘을 이용하여 HTTP 통신을 하는 과정에서 데이터를 암호화하여 전송한다
  • 접속하는 웹사이트의 왼족 자물쇠를 클릭하면 보안 정보가 나타난다

 

2. HTTPS 특징

 1) 암호화

  • 클라이언트와 서버가 데이터를 암호화하여 주고받기 위해 비대칭키 방식과 대칭키 방식을 혼용하여 사용한다
  • 대칭키 방식은 양쪽이 공통의 비밀 키를 공유하여 데이터를 암호화 및 복호화한다
  • 비대칭키 방식은 각각 공개키(public key)와 비밀키(private key)를 가지고 있어서 상대가 공개키로 암호화한 데이터를 개인이 가진 비밀키로 복호화한다
    - 전자서명은 개인키로 암호화하고 공개키로 복호화 한다
    - 암호화는 공개키로 암호화하고 개인키로 복호화 한다

  • 클라이언트와 서버는 한 쌍의 비대칭 암호화 Key를 가지고 있다
  • 클라이언트와 서버 간의 암호화 key를 상호 검증하여 보안을 강화한다
  • 서버가 실행되면서 인증서를 등록하므로 클라이언트는 서버의 공개키를 알 수 있다

 

2) 인증서(Certificate)

  • 데이터 제공 서버가 실제로 데이터를 보내는 서버인지 확인하는 용도이다
  • 서버의 도메인 정보가 포함되어 있어서 데이터 제공자의 인증을 용이하게 한다
  • 서버에서 보내는 응답 정보와 인증서를 함께 확인하여 보안을 강화한다

 

 3) CA(Certificate Authority)

  • 공인 인증서 발급 기관을 CA(Certificate Authority)라고 한다
    - CA는 공인 인증 기관으로 엄격한 기준에 따라 자격이 유지되고 박탈 당할 수 도 있다
    - CA들은 서버의 공개키와 정보를 CA의 비밀키로 암호화하여 인증서를 발급한다
  • 브라우져는 인증서 발급 기관으로부터 발급받은 인증서를 보유하고 있다

Certificate 예시

  • 서버와 클라이언트 간의 CA를 통해 서버를 인증하고 데이터를 암호화하는 과정의 전체 프로토콜을 TLS 또는 SSL이라고 한다
    - SSL과 TLS는 사실상 동일한 규약을 뜻한다
    - SSL이 표준화되며 변경된 이름이 TLS이다

 

3. 인증서 발급

 1) 인증서 지원 형식

  • 자바는 다음과 같은 두 가지의 인증서 형식을 지원한다
    - PKCS12 (Public Key Cryptographic Standards #12)
     : 여러 인증서와 키를 포함할 수 있으며, 암호로 보호된 형식으로 업계에서 널리 사용된다

    - JKS (Java KeyStore)
     : PKCS12와 유사하다
     : 독점 형식이며 Java 환경으로 제한된다

 2) HTTPS 사설 인증서 발급 

  • Spring Initializr에서 기본 웹 서버 프로젝트를 생성한다
  • mkcert 프로그램을 설치한다
    - mkcert 프로그램을 이용해서 로컬 환경(내 컴퓨터)에서 신뢰할 수 있는 인증서를 만들 수 있다
    - mkcert는 PKCS12 형식만 지원합니다.
  • mkcert 프로그램 설치 방법
    - 터미널에 아래 명령어를 입력 후 실행하여 설치한다
WSL 터미널에서 작성한다

$ sudo apt install libnss3-tools

      - 설치가 안되고 에러가 발생할 경우 update를 먼저 진행한다

$ sudo apt update

      - 설치가 되었으면 아래 명령을 입력하고 진행한다

$ wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64

      - 마지막으로 아래 명령을 입력해 준다

$ chmod +x mkcert
$ sudo cp mkcert /usr/local/bin/

  • 로컬을 인증된 발급기관으로 추가해 준다
    - 아래 명령어를 작성 후 실행하여 CA를 생성한다
$ mkcert -install

  •  PKCS12 인증서를 생성하기 위해 아래 명령을 작성 후 실행한다
$ mkcert -pkcs12 localhost

  • 옵션으로 추가한 localhost에서 사용할 수 있는 인증서가 완성되었다
  • 해당 커맨드를 입력한 위치에 localhost.p12라는 파일이 생성된 것을 확인할 수 있다
  • 저장 경로를 반드시 확인한다 

3) HTTPS 서버 작성

  • Spring Boot를 이용하여 HTTPS 서버를 간단하게 작성할 수 있다
    - 생성된 인증서를 resources 폴더로 이동한다

     - application.properties 에서 관련 설정을 추가한다
      → 비밀번호인 changeit은 비밀번호를 설정하지 않았을 때의 기본값이다
       인증서 비밀번호는 인증서를 생성할 때 설정하거나 생성 후 변경해 줄 수 있다
       비밀번호 설정 방법은 한번 찾아보시기 바랍니다 :)

server.ssl.key-store=classpath:localhost.p12    -> 인증서 경로를 적는다
server.ssl.key-store-type=PKCS12                -> 인증서 형식을 적는다
server.ssl.key-store-password=changeit          -> 인증서 비밀번호를 적는다
  • 이제 서버를 실행해 보면 HTTPS 서버가 작동되는 것을 확인할 수 있다

  • web애플리케이션에서도 확인해 본다
    - 페이지를 생성한 것이 없으므로 white error 가 발생한다

 

 

 

 

 

※ 참고 자료

▶ 중간자 공격 : https://en.wikipedia.org/wiki/Man-in-the-middle_attack

 

Man-in-the-middle attack - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Form of message tampering In cryptography and computer security, a man-in-the-middle, monster-in-the-middle,[1][2] machine-in-the-middle, monkey-in-the-middle,[3] meddler-in-the-middle

en.wikipedia.org

▶ 암호화 : (101) HTTPS가 뭐고 왜 쓰나요? (Feat. 대칭키 vs. 비대칭키) - YouTube

 교차 출처 리소스 공유 (CORS) : https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
웹 보안 공격  (0) 2022.07.23
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23

+ Recent posts