반응형
반응형

📝LangChain

LLM을 활용한 애플리케이션을 쉽게 개발할 수 있도록 도와주는 프레임워크로 RAG를 구현하는 데 아주 유용한 도구입니다.

 

📝RAG (Retrieval-Augmented Generation)

RAG는 정보 검색 기반 생성 모델을 의미하며 아래 두 가지 프로세스를 결합한 구조입니다.

  1. Retrieval(정보 검색)
    • 외부 데이터베이스나 문서에서 필요한 정보를 검색
    • OpenAI GPT 같은 LLM이 기존에 학습하지 않은 최신 데이터나 특정 도메인 데이터를 활용할 수 있도록 함
  2. Generation(생성)
    • 검색된 정보를 기반으로 자연어 답변을 생성

 

RAG 작동 원리

  1. 질문 입력 사용자가 질문을 입력
  2. 정보 검색 검색 엔진이나 벡터 데이터베이스를 사용해 관련 정보를 검색
  3. 생성 단계 검색된 정보를 언어 모델에 전달하여 적합한 답변 생성

 

RAG의 장점

  • 최신 정보나 사전 학습 데이터에 없는 데이터에 접근 가능
  • 모델의 응답 정확도를 높이고 불필요한 "환각(hallucination)"을 줄임.

 

📝자연어

자연어(natural language)란 우리가 일상 생활에서 사용하는 언어를 말합니다. 자연어 처리(natural language processing)란 이러한 자연어의 의미를 분석하여 컴퓨터가 처리할 수 있도록 하는 일을 말합니다.

 

📝텐서플로우(Tensorflow)

텐서플로우는 구글이 2015년에 공개한 머신 러닝 오픈소스 라이브러리입니다. 머신 러닝과 딥러닝을 직관적이고 손쉽게 할 수 있도록 설계되었습니다.

 

📝케라스(Keras)

케라스(Keras)는 딥러닝 프레임워크인 텐서플로우에 대한 추상화 된 API를 제공합니다. 쉽게 말해, 텐서플로우 코드를 훨씬 간단하게 작성할 수 있습니다.

 

📝젠심(Gensim)

젠심(Gensim)은 머신 러닝을 사용하여 토픽 모델링과 자연어 처리 등을 수행할 수 있게 해주는 오픈 소스 라이브러리입니다.

 

📝사이킷런(Scikit-learn)

사이킷런(Scikit-learn)은 파이썬 머신러닝 라이브러리입니다. 사이킷런을 통해 나이브 베이즈 분류, 서포트 벡터 머신 등 다양한 머신 러닝 모듈을 불러올 수 있습니다. 또한, 사이킷런에는 머신러닝을 연습하기 위한 아이리스 데이터, 당뇨병 데이터 등 자체 데이터 또한 제공하고 있습니다.

 

📝NLTK

엔엘티케이(NLTK)는 자연어 처리를 위한 파이썬 패키지입니다.

 

📝KoNLPy

코엔엘파이(KoNLPy)는 한국어 자연어 처리를 위한 형태소 분석기 패키지입니다.

 

📝JPype

JAVA와 Python을 연결해주는 역할을 합니다.

반응형
반응형

📝 MCU(Microcontroller Unit)

MCU는 프로세서(CPU), 메모리(RAM, ROM), 입출력 장치(I/O)를 하나의 칩에 통합한 시스템

  • 에어컨
  • IoT
  • 등...

 

📝 MPU(Microprocessor Unit)

MPU는 CPU 역할만 하며, RAM/ROM/I/O는 외부 연결이 필요하다. CPU와 헷갈릴 수 있는데 MPU의 경우 연산처리만하고 따로 보조적인 기억장치등이 포함되어있지 않다. 연산에만 핵심을 두기 때문에 CPU에 비해 저전력이긴하다.

  • 스마트폰
  • 등...

 

📝 CUDA (Compute Unified Device Architecture)

NVIDIA가 개발한 GPU 병렬 컴퓨팅 플랫폼 및 프로그래밍 모델입니다.
GPU를 활용한 고속 연산을 가능하게 하는 소프트웨어 프레임워크

  1. 병렬 연산 가능
  2. 딥러닝/AI 가속

 

📝 PTX (Parallel Thread Execution)

CUDA에서 사용되는 중간 수준의 GPU 명령어(Intermediate Representation, IR)입니다.

 

📝 NVLink

NVIDIA가 개발한 GPU 간 초고속 데이터 전송 기술

 

📝 MOE 아키텍처

AI 모델에서 일부 전문가 네트워크(Experts)만 활성화하여 연산량을 줄이는 신경망 아키텍처입니다.
예를 들면 여러 파라미터를 가지고 있고 정답에 필요한 부분의 파라미터만 알아서 활성화시키는 전략입니다.

 

📝 HBM

HBM(High Bandwidth Memory)은 초고속 대역폭을 제공하는 고성능 메모리 기술

반응형
반응형

📝온디바이스

서버나 클라우드에 연결할 필요 없이 로컬에서 자체적으로 정보를 처리할 수 있는 것

📝SFT(Supervised Fine-Tuning)

지도학습으로 모델을 특정한 데이터셋에 맞춰 미세 조정하는 과정으로 사전에 준비된 입력(Input)과 정답(Output) 쌍을 모델에게 학습시키는 방식이다. 모델이 올바른 답변을 예측하도록 지도 데이터(Label)가 있는 데이터셋을 사용

 

📝RL(Reinforcement Learning)

모델이 행동(Action)을 수행하고 이에 대한 보상(Reward)을 받으면서 학습하는 방식으로 정답이 직접 주어지는 것이 아니라, 보상을 기반으로 최적의 행동을 찾아간다. 그리고 나온 결과를 다시 입력하는 방식을 이용한다.

 

📝RLHF (Reinforcement Learning from Human Feedback)

강화학습할 때 사람의 피드백으로 보상을 활용하는 방식으로 "좋은 답변"을 사람이 직접 골라주는 RL 방식

 

📝 프론티어 모델

가장 앞선(Frontier) 수준의 인공지능 모델을 의미하며, 보통 최첨단 대형 AI 모델을 지칭하는 용어입니다.
예를 들면 현재 수준에서는 GPT-4, GPT-5를 의미한다. 얘네가 있어야 다음 시리즈를 만들 때 더 좋은 성능을 낸다.

 

📝 스케일링법칙

AI 모델이 커질수록(더 많은 파라미터, 데이터, 연산량을 사용할수록) 성능이 예측 가능한 방식으로 향상된다는 경험적인 법칙 예를 들면 모델 크기(파라미터 수) 증가 → 성능 비약적 향상

 

📝 FP32, FP16, FP8, FP4

FP란 Floating Point로 부동소수점(소수점이 있는 실수 표현 가능)을 의미한다. 뒤에 숫자는 비트를 의미하며 더 숫자가 클수록 비트가 커져서 더 많은 데이터를 보관해 정밀도가 올라가지만 연산 속도가 느려지게 된다.

 

📝 토큰 가격

AI에서 제공하는 API를 사용할 때 입력과 출력에 따라 부과되는 비용

반응형
반응형

📝양자컴퓨터

양자역학의 원리를 활용해 계산을 수행하는 새로운 방식의 컴퓨터입니다.

전통적인 컴퓨터가 사용하는 비트(bit) 대신, 양자 컴퓨터는 양자 비트(큐비트, qubit)를 사용해 정보를 처리합니다.

큐비트는 0과 1 사이의 모든 상태를 동시에 가질 수 있습니다.

 

예를들면 3개의 고전 비트는 한 번에 하나의 상태(예: 000, 001, ... 등 8개 중 하나)를 표현하지만, 3개의 큐비트는 한 번에 모든 상태를 동시에 표현할 수 있습니다. 이를 통해 양자 컴퓨터는 병렬로 여러 계산을 동시에 수행할 수 있습니다. 큐비트가 늘어날수록 연산 능력이 지수적으로 증가합니다.

 

양자 컴퓨터는 확률적으로 정답을 찾아갑니다. 확률이 높다고 무조건 정답은 아니지만 이를 보완하기 위해 매우 많은 시도를 동시에 진행한다. (검산과정) → 간섭이 많을 수록 확률이 높다 (정답 가능성이 매우 높음)

 

 

📝Qubit(큐비트)

정보를 저장하고 처리하는 양자적 단위

 

 

📝 양자 푸리에 변환(QFT)

양자 푸리에 변환의 경우 일상생활 문제를 해결하기 위한 양자역학의 시도입니다. 입력 데이터(시간 또는 공간에 따른 함수)를 주파수 기반의 성분으로 분해하는 과정을 의미합니다.

주기성 탐지, 최적화 문제 해결에 탁월하다 주 사용 분야는 양자 컴퓨팅이며 상태를 주파수 영역으로 변환시켜 사용한다.  주기가 있는 즉, 주기가 있다는 건 특징이 있다는 것이고 특징이 있는 곳에 활용하기 좋다는 말입니다.

 

 

📝쇼어 알고리즘

1994년 피터 쇼어(Peter Shor)가 제안한 양자 알고리즘으로, 큰 정수를 빠르게 소인수분해하는 방법입니다.

양자컴퓨터에서 중요한데 고전 컴퓨터에서는 소인수분해는 매우 시간이 많이 걸리는 작업인데 특히 큰 수는 소인수 분해가 사실상 불가능합니다. 하지만 해당 알고리즘은 이 문제를 효율적으로 해결할 수 있습니다.

 

RSA는 소인수 분해의 "어려움"을 기반으로 한 암호화 체계로 보안에 문제를 일으킬 수도 있다는 말이 있습니다.

 

 

📝그로버 알고리즘

양자 컴퓨팅에서 사용되는 검색 알고리즘으로, 미지의 데이터베이스에서 원하는 항목을 빠르게 찾는 문제를 해결합니다. 이는 고전적인 검색 알고리즘보다 제곱근 속도로 빠릅니다

 

 

🔗 참고 및 출처

https://www.youtube.com/watch?v=iWnqT_Zv6q8

https://www.youtube.com/watch?v=ScBscK0wEI4

 

반응형
반응형

📝인공지능

컴퓨터가 인간처럼 사고, 학습, 문제를 해결, 판단할 수 있도록 설계된 기술입니다.

 

📝머신러닝

데이터를 학습하여 특정 작업을 수행할 수 있도록 컴퓨터를 훈련시키는 기술입니다. 인간은 데이터를 넣어주고 학습방법에 따라 훈련시킵니다. 예를 들면 고양이 사진을 주고 고양이 특징을 알려줬을 때 다른 고양이 사진을 주면 고양이라고 판단합니다.

 

훈련기술은 아래와 같습니다.

 

  • 지도학습
    • 입력 데이터와 정답이 주어진 상태에서 학습 → 이메일 스팸 필터
  • 비지도학습
    • 정답이 없는 데이터를 분석하고 패턴을 학습 → 이미지 분류
  • 강화학습
    • 보상을 기반으로 최적의 행동을 학습 → 알파고(바둑)

 

 

📝딥러닝

머신러닝의 하위 분야로 인공 신경망을 기반으로 데이터를 학습시키는 기술입니다. 머신러닝과 달리 사람의 개입 없이 중요한 특징을 알아서 추출합니다. 예를 들면 고양이 사진을 여러개를 주면 알아서 특징을 파악하고 다른 고양이 사진을 줬을 때 얘가 고양인지 파악할 수 있습니다.

 

  • 입력층
    • 입력데이터를 신경망에 전달하는 첫번째 층이다. 데이터만 전달하며 함수나 가중치 계산은 없다.
  • 은닉층
    • 입력 데이터를 변환하고 학습하는 신경망의 중간층이다. 은닉층이 많아질수록 더 복잡한 패턴 학습이 가능하다. 일반적으로 그렇지만 많은 시도를 해서 자기가 원하는 최적의 결과를 얻을때까지 해야한다. (사실 나도 안 해봐서 잘 모름)
  • 출력층
    • 신경망의 최종 결과를 출력하는 층이다.

 

📝머신러닝 vs 딥러닝

특징 머신러닝 딥러닝
데이터 처리 사람이 데이터를 분석하고 특징을 수동 정의 신경망이 데이터를 입력받아 특징을 자동으로 추출
데이터 요구량 비교적 적은 데이터로도 학습 가능 방대한 데이터 필요 (방대할 수록 성능이 올라간다)
컴퓨팅 자원 적은 연산 자원으로도 학습 가능 고성능 GPU 필요
비정형 데이터 처리 주로 구조화된 데이터(엑셀, 테이블) 처리에 적합 이미지, 텍스트, 음성 등 비정형 데이터를 처리 가능
특징 추출 사람이 직접 특징을 추출하게끔 설계 자동으로 특징 추출

 

 

📝LLM (대규모 언어 모델)

대규모 데이터로 학습된 언어 모델로, 자연어를 이해하고 생성하는 데 사용됩니다. LLM은 특히 수십억에서 수조 개의 매개변수(parameters)를 가진 신경망 모델로, 복잡한 언어적 패턴을 학습할 수 있습니다.

 

📝생성형 AI

데이터를 학습하여 새로운 콘텐츠(텍스트, 이미지, 음악 등)를 생성할 수 있는 AI 기술입니다. LLM과 딥러닝 기술을 이용해 텍스트, 이미지, 음성 등을 생성합니다.

 

 

📝자연어 처리

인간의 언어(자연어)를 컴퓨터가 이해하고 생성하며 처리할 수 있도록 하는 기술입니다.

반응형
반응형

📝절차지향 vs 객체지향

절차지향 vs 객체지향의 경우 인프콘에서 컨퍼런스한 내용을 기반으로 작성했습니다

 

절차지향과 객체지향에 차이를 설명하기에 앞서 예제를 준비했습니다. (Java 기반 코드)

장바구니의 전체 금액 >= 할인 기준금액일 경우 할인 프로모션을 적용시키는 시스템을 만드려고 합니다.

 

절차지향의 경우는 아래와 같습니다

// 할인
public class Promotion {
    private Long cartId;
    private Long basePrice;
    
    public Money getBasePrice() {
    	return basePrice;
    }
    public void setCart(Long cartId) {
   	 this.cartId = cartId;
    }
}

// 장바구니
public class Cart {
    private List<CartLineItem> items = new ArrayList<>();
    
    public Long getTotalPrice() {
    	return items.stream().mapToLong(CartLineItem::getPrice).sum();
    }
    public int getTotalQuantity() {
    	return items.stream().mapToInt(CartLineItem::getQuantity).sum();
    }
}

// 할인 프로세스
public class PromotionProcess {
    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart);
        }
    }
    private boolean isApplicableTo(Promotion promotion, Cart cart) {
    	return cart.getTotalPrice() >= promotion.getBasePrice();
    }
}

절차지향의 경우 할인, 장바구니의 데이터영역과 할인 프로세스를 처리하는 프로세스영역으로 나누어져있습니다.

 

 

객체지향의 경우는 아래와 같습니다.

// 할인 + 할인프로세스
public class Promotion {
    private Cart cart;
    private Long basePrice;
    
    public void apply(Cart cart) {
    	if (cart.getTotalPrice() >= basePrice) {
    	this.cart = cart;
    	}
    }
}

// 장바구니
public class Cart {
    private List<CartLineItem> items = new ArrayList<>();
    
    public Long getTotalPrice() {
    	return items.stream().mapToLong(CartLineItem::getPrice).sum();
    }
    public int getTotalQuantity() {
    	return items.stream().mapToInt(CartLineItem::getQuantity).sum();
    }
}

절차지향과 다르게 객체지향은 데이터와 프로세스 영역으로 나누어져있지 않습니다. 할인과 관련된 것들끼리 다 모여있고 장바구니 관련된 것끼리 다 모여있습니다.

 


절차지향과 객체지향의 차이를 이야기할 때는 변경이라는게 중요합니다. 어떤 변경에 어떤 방식이 유연한지에 대한 것입니다. 위에 예제 기반으로 더 진행하자면 이번에는 최소금액 <= 장바구니금액 <= 최대금액일 경우에 할인이 적용되게끔 변경해달라는 요청이 있을 때 처리하는 방법입니다.

 

 

절차지향의 경우 아래와 같습니다.

// 할인 적용 프로세스
public class PromotionProcess { 
    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart);
        }
    }
    
    private boolean isApplicableTo(Promotion promotion, Cart cart) {
    	return cart.getTotalPrice() >= promotion.getMinPrice() 
        &&     cart.getTotalPrice() <= promotion.getMaxPrice()
    }
}

// 할인
public class Promotion {
    private Long cartId;
    private Long minPrice;
    private Long maxPrice;
    
    public Long getMinPrice() {
        return minPrice;
    }
    public Long getMaxPrice() {
    	return maxPrice;
    }
    public void setCart(Long cartId) {
        this.cartId = cartId;
    }
}

절차지향의 경우 할인 적용시킬 기준 금액인 최소금액과 최대금액을 추가시켰습니다. Promotion과 PromotionProcess 두군데에서 수정이 이루어졌습니다.

 

 

객체지향의 경우 아래와 같습니다.

public class Promotion {
    private Cart cart;
    private Long minPrice;
    private Long maxPrice;
    
    public void apply(Cart cart) {
    	if (cart.getTotalPrice() >= basePrice &&
    		cart.getTotalPrice() >= maxPrice) {
        	this.cart = cart;
    	}
    }
}

객체지향의 경우 Promotion에 Promotion관련 필드와 로직이 다 들어있기 때문에 Promoiton 한군데에서만 수정이 이루어졌습니다.

 


변경사항이 또 들어왔습니다. 이번에는 할인 조건을 하나 더 추가하는데 장바구니의 상품수  >= 프로모션 상품수일 경우 할인을 해줍니다. 할인을 받기 위해서는 설정한 프로모션 상품수보다 장바구니에 더 많이 담으면 할인을 해준다는 것이죠

 

 

절차지향의 경우 아래와 같습니다.

// 할인 프로세스
public class PromotionProcess {
    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
    		promotion.setCart(cart);
    	}
    }
    
    private boolean isApplicableTo(Promotion promotion, Cart cart) {
        switch (promotion.getConditionType()) {
            case PRICE:
            	return cart.getTotalPrice() >= promotion.getBasePrice();
            case QUANTITY:
                return cart.getTotalQuantity() >= promotion.getBaseQuantity
        }
    	return false;
    }
}

// 할인
public class Promotion {
    public enum ConditionType {
    	PRICE, QUANTITY
    }

    private Long cartId;
    private Long basePrice;
    private int baseQuantity;

    public ConditionType getConditionType() {
    	return conditionType;
    }
    public Money getBasePrice() {
   	 return basePrice;
    }
    public int getBaseQuantity() {
    	return baseQuantity;
    }	
    public void setCart(Long cartId) {
    	this.cartId = cartId;
    }
}

절차지향의 경우는 가격 또는 개수에 따른 할인 조건이 두개라서 CondtionType을 할인 모델에 추가시켰습니다. 그리고 할인을 위한 기준 금액과 기준 개수가 필요하기 때문에 basePrice와 baseQuantity를 만들어줬습니다.

 

이럴경우 되게 코드가 지저분해보이며 모델도 추가해줘야하고 프로세스도 수정해야하는 여러군데에서 수정이 필요합니다. 또 할인 유형이 생기는 경우 더 늘어나게 됩니다.

 

객체지향의 경우 아래와 같습니다.

// 할인
public class Promotion {
    private Cart cart;
    private DiscountCondition condition;
    
    public void apply(Cart cart) {
    	if (condition.isApplicableTo(cart)){
        	this.cart = cart;
        }
    }
}

// 장바구니
public class Cart {
	private List<CartLineItem> items = new ArrayList<>();

	public Long getTotalPrice() {
		return items.stream().mapToLong(CartLineItem::getPrice).sum();
	}
	public int getTotalQuantity() {
		return items.stream().mapToInt(CartLineItem::getQuantity).sum();
	}
}

// 할인 인터페이스
public interface DiscountCondition {
	boolean isApplicableTo(Cart cart);
}

// 가격 할인 구현체
public class PriceCondition implements DiscountCondition {
    private Long basePrice;
    
    @Override
    public boolean isApplicableTo(Cart cart) {
    	return cart.getTotalPrice() >= basePrice;
    }
}

// 개수 할인 구현체
public class QuantityCondition implements DiscountCondition {
    private int baseQuantity;
    
    @Override
    public boolean isApplicableTo(Cart cart) {
    	return cart.getTotalQuantity() >= baseQuantity;
    }
}

객체지향의 경우 다형성을 이용해야합니다. 그렇기 때문에 interface를 만들어줍니다. Promotion의 apply에서는 interface를 받게끔 해줍니다.

 

파일은 많아집니다만 이런식으로 객체지향식으로 설계가 되어있는 상태에서 다른 할인 유형에 대한 처리가 필요한 경우 DiscountCondition을 상속받아 구현하면 됩니다.

 

Promotion에서 apply는 수정할 필요도 없습니다. DiscountCondition을 상속받은 구현체만 껴주기만 하면 됩니다. 그리고 Promotion을 건들 필요가 없기 때문에 실질적으로 DiscountCondition을 상속받아서 만들어주기만 하면 됩니다. 파일 수정도 한군데에서만 일어나게 됩니다. 이곳저곳 수정할 필요도 없고 DiscountCondition을 무조건 받아야하기 때문에 절차적인 것처럼 임의대로 막 만들수도 없어 개발이 체계적으로 보입니다.

 


위와 같은 상황인 같은 기능을 다른로직으로 갈아껴야하는 상황에는 객체지향이 유리합니다만 무조건적으로 객체지향이 유리하지는 않습니다. 새로운 요구사항을 추가해봅시다. 장바구니에 대한 할인이였는데 이번에는 장바구니에 있는 하나의 상품에 대한 할인 적용을 만들어봅시다. 이번에는 상품인 Item에 대한 모델은 따로 안 만들고 할인에 대한 프로세스에만 집중해봅시다.

 

 

절차지향의 경우 아래와 같습니다.

// 할인 프로세스
public class PromotionProcess {
	public void apply(Promotion promotion, Cart cart) {
		if (isApplicableTo(promotion, cart)) {
			promotion.setCart(cart);
		}
	}

	private boolean isApplicableTo(Promotion promotion, Cart cart) {
		switch (promotion.getConditionType()) {
			case PRICE:
				return cart.getTotalPrice() >= promotion.getBasePrice();
			case QUANTITY:
				return cart.getTotalQuantity() >= promotion.getBaseQuantity();
		}
		return false;
	}

	// 새로 추가된 상품에 대한 할인 적용
	public boolean isApplicableTo(Promotion promotion, CartLineItem item) {
		switch (promotion.getConditionType()) {
			case PRICE:
				return item.getTotalPrice() >= promotion.getBasePrice();
			case QUANTITY:
				return item.getTotalQuantity() >= promotion.getBaseQuantity();
		}
		return false;
	}
}

절차지향의 경우 상품을 매개변수로 받아야 하기 때문에 또 다른 함수를 만듭니다. 다른 파일 건들 필요 없이 간단하게 PromotionProcess에만 추가하면 됩니다.

 

 

객체지향의 경우 아래와 같습니다.

// 할인
public class Promotion {
    private Cart cart;
    private DiscountCondition condition;
    
    public void apply(Cart cart) {
        if (condition.isApplicableTo(cart)) {
            this.cart = cart;
        }
    }
}

// 할인 인터페이스
public interface DiscountCondition {
    boolean isApplicableTo(Cart cart);
    boolean isApplicableTo(CartLineItem item);
}

// Price 할인 구현체
public class PriceCondition implements DiscountCondition {
    private Long basePrice;
    
    @Override
    public boolean isApplicableTo(Cart cart) {
	    return cart.getTotalPrice() >= basePrice;
    }
    
    @Override
    public boolean isApplicableTo(CartLineItem item) {
    	return item.getPrice() >= basePrice;
    }
}

// Quantity 할인 구현체
public class QuantityCondition implements DiscountCondition {
    private int baseQuantity;
    
    @Override
    public boolean isApplicableTo(Cart cart) {
    	return cart.getTotalQuantity() >= baseQuantity;
    }
    
    @Override
    public boolean isApplicableTo(CartLineItem item) {
    	return item.getQuantity() >= basePrice;
    }
}

객체지향의 경우 Interface의 함수를 추가해야하고 이걸 무조건 구현해야하기 때문에 이걸 상속받은 구현체들의 코드는 다 추가되어야합니다. 또한 여기선 Promotion에 코드를 추가 안 했지만 Promotion 코드도 추가해야하기 때문에 총 4개의 파일을 수정해야합니다.

 

 

 


위와 같이 로직이 아예 다른 것을 추가할 때 객체지향의 경우 더 불리합니다. (장바구니할인 → 상품할인) 또 다른 예로는 타입 계층 전체 수정이 있는 경우입니다. Cart와 Promotion 모델을 합쳐서 CartWithPromotion 모델을 만들어야하는 예를 들어봅시다.

 

 

절차지향의 경우 아래와 같습니다.

public class PromotionProcess {

    public CartWithPromotion convertToCartWithPromotion(
	Promotion promotion,
    Cart cart) {
        CartWithPromotion result = new CartWithPromotion();
        result.setTotalPrice(cart.getTotalPrice());
        result.setTotalQuantity(cart.getTotalQuantity());
        result.setPromotionBasePrice(promotion.getBasePrice());
        result.setPromotionBaseQuantity(promotion.getBaseQuantity());
        return result;
    }
}

절차지향의 경우는 되게 간단하게 처리가 가능합니다. 두개의 모델을 받아서 그냥 새로운 모델에 넣어서 반환하면 됩니다.

 

 

 

객체지향의 경우 아래와 같습니다.

public class Promotion {
    private Cart cart;
    private DiscountCondition condition;
    ...
    
    public CartWithPromotion convertToCartWithPromotion() {
        CartWithPromotion result = new CartWithPromotion();
        result.setTotalPrice(cart.getTotalPrice());
        result.setTotalQuantity(cart.getTotalQuantity());
        
        if (condition instanceof PriceCondition) {
            result.setPromotionBasePrice(
            ((PriceCondition)condition).getBasePrice());
        }
        if (condition instanceof QuantityCondition) {
            result.setPromotionBaseQuantity(
            ((QuantityCondition)condition).getBaseQuantity());
        }
    	return result;
    }
}

객체지향의 경우는 되게 복잡하게 처리해야합니다. Interface로 다형성을 유지해야하기 때문에 어떤 구현체가 들어올지 몰라 instanceof를 통해 클래스를 확인한 이후에 각각 다르게 처리해야합니다. 복잡하고 되게 어색합니다.

 


최종적으로 정리해 간단 요약하면 아래 표와 같습니다.

절차적인 설계 객체지향 설계
포맷 변경을 위한 데이터 변환 (데이터 합치기) 규칙에 기반한 상태 변경 (체계적) [복잡한 설계에 유리]
데이터 중심 행동 중심
데이터 노출 데이터 캡슐화
기능 추가에 유리 (아예 새로운 기능 → 장바구니가 아닌 상품 할인 기능이 필요한 경우) 타입 확장에 유리 (본질적인 기능은 같지만 다양하게 확장 가능 → 인터페이스로 인한 할인 확장)
데이터와 프로세스가 명확히 분리 데이터 프로세스 같이 존재 (클래스 안에 할인 관련된 데이터와 할인 프로세스가 같이 들어있다)

 

 

 

  • 도메인 레이어의 경우 객체지향이 좋습니다. (어떤 할인 내용을 적용시킬지 = 할인이라는 공통 분모)
  • 프레젠테이션 레이어의 경우 데이터를 반환하기 때문에 절차지향에 좋습니다.
  • 서비스 레이어의 경우 비즈니스로직순차적 처리가 필요하기 때문에 절차지향에 좋습니다. (서비스에 어떤 할인을 적용시킬지 이런 내용인 인터페이스를 상속받은 구현체가 들어가서 절차지향에 객체지향이 들어간 형태입니다.)
  • 퍼시스턴스 레이어의 경우 DB 쿼리문 조회하고 이런 부분은 순차 처리가 필요하기 때문에 절차지향에 좋습니다. 

 

이미 우리는 상황에 맞게 절차지향과 객체지향을 섞어 쓰고 있습니다. 즉, 우월하다 이런 건 없고 상황에 맞게 잘 써야합니다.

 

 

📝 인터페이스 vs 추상클래스

 

인터페이스

public interface Animal {
    void eat();  // 메서드 선언 (구현 X)
    void sleep();
}

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog eats.");
    }

    @Override
    public void sleep() {
        System.out.println("Dog sleeps.");
    }
}

인터페이스의 특징은 아래와 같습니다.

  • 다중 상속 가능
  • 행동의 규약 정의 → 해당 메소드는 반드시 구현해야 함
  • 무엇을 해야하는 가를 정의한다.

 

 

추상클래스

public abstract class Animal {
    private String name;  // 멤버 변수

    public Animal(String name) {
        this.name = name;
    }

    public void breathe() {
        System.out.println(name + " is breathing."); // 일반 메서드
    }

    public abstract void eat();  // 추상 메서드
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println("Dog eats.");
    }
}

추상클래스의 특징은 아래와 같습니다.

  • 단일 상속
  • 추상클래스에서 이미 구현되어있기 때문에 상속받아서 직접 구현 안 해도 된다.
  • 기본적인 공통기능에 사용하며 만약 커스텀이 필요한 경우 @overrid로 재정의해서 사용하면 좋다.
  • 공통메서드를 사용하거나 커스텀이 필요한 경우 @override 재정의가 가능하기 때문에 interface처럼 규약에 덜 얽매인다.

 

 

🔗 참고 및 출처

https://www.youtube.com/watch?v=usOfawBrvFY&t=446s

https://cactuslog.tistory.com/

 

 

 

반응형
반응형

📝withCredentials

서로 다른 도메인에 요청을 보낼때 요청에 Credentials 정보를 담아서 보낼지에 대한 여부입니다.

기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있다. 이는 응답을 받을때도 마찬가지이다.  따라서 요청과 응답에 쿠키를 허용하고 싶을 경우, 이를 해결하기 위한 옵션이 바로 withCredentials 옵션을 넣어야한다

 

credential 정보가 포함된 요청은 아래와 같습니다.

  1. 쿠키를 첨부해서 보내는 요청
  2. 헤더에 Authorization 항목이 있는 요청

 

📝HttpOnly

자바스크립트로부터 쿠키 접근을 차단하여 XSS 공격 방지를 해준다

 

📝Secure

HTTPS를 통해서만 쿠키를 전송하여 MITM 공격 방지할 수 있다

 

📝SameSite

쿠키가 서로 다른 사이트에서 전송되지 않도록 제한 하는 속성으로 CSRF 공격을 방지하는데 사용됩니다.

 

  • Strict
    • 쿠키가 같은 사이트 내에서만 전송됩니다. 외부 사이트에서 링크 클릭하여 들어오는 경우 쿠키가 전송되지 않습니다.
  • Lax
    • 쿠키가 같은 사이트 내에서만 전송됩니다. 외부 사이트에서 gET 요청이 발생한 경우에는 쿠키가 전송 될 수 있습니다. → 예를 들면 다른 곳에서 쿠팡 링크로 들어왔을 때 어떤 검색어를 통해서 들어왔는지에 대한 쿠키에 대한 정보를 파악하기 위해 필요하다
  • None
    • 쿠키가 모든 사이트 간 전송될 수 있습니다. 이 옵션을 사용할 때는 Secure 속성을 함께 사용해야합니다.

 

 

 

 

 

반응형
반응형

JWT를 이용해 인증, 인가를 구현하는 방법에 대해 설명한다.

 

로그인

  1. 로그인하기
    • 클라이언트에서 아이디, 패스워드 전달
  2. 서버에서 패스워드 암호화 후 DB에서 아이디, 패스워드 검증
  3. 서버에서 Access Token, Refresh Token 토큰 발행
    • Access Secret Key, Refresh Secret Key 각각 다르게 생성
  4. 서버에서 Refresh Token DB 저장
    • 추후 서버에서 강제 로그아웃하기 위해 필요
  5. 서버에서 Response 쿠키에 HttpOnly 사용상태로 리프레시 토큰 저장 후 클라이언트로 보내기
  6. 서버에서 클라이언트로 Access Token 값 보낸후 클라이언트에서 메모리(Redux) 저장

 

권한 필요한 API 통신

일반적으로 토큰은 요청 헤더의 Authorization 필드에 담겨서 보내지게 되는데 JWT에 해당하는 type은 Bearer입니다 → Authorization: <type> <credentials>

Global Guard에 Guards를 적용시키고 필요하지 않은 건 Public Decorator로 Pass하게 한다 (로그인, 회원가입 기능 등…)

Admin만 호출할 수 있는 게 있으면 Role에 담아서 보낸다

  1. 클라이언트에서 요청시 헤더에 Authorization: Bearer ${Access Token}을 붙여서 Access Token과 함께 API 요청을 하고 쿠키의 Refresh Token 내용을 같이 보낸다.
  2. 서버에서 Access Token을 검증하며 정상일 땐 그대로 처리한다 비정상일 땐 Access Token 만료 시나리오대로 진행한다.
  3. Refresh Token이 만료된 경우는 Refresh Token 만료 시나리오대로 한다.

 

권한에 따른 페이지 접근 처리

  1. Jwt의 payload 데이터에 해당 사용자의 역할 정보를 넣어서 암호화 시킨다.
  2. Access Token 발급 받을 때 Header에 Role 정보를 보낸다
  3. Role정보에 따라 페이지를 달리 처리한다
    • Next.js의 경우 admin이라는 라우팅하나 만들고 layout안에 검증식을 넣어놨다

 

Access Token 만료

잘못된 토큰 요청인지 만료되었는지를 클라이언트에게 보여줄 필욘 없기 때문에 유효하지만 않는지 체크만 하면 된다.

  1. 클라이언트에서 서버에 HTTP 요청을 보낸다
    • 모든 요청에는 Access Token은 Bearer에 담아서 보내고 Refresh Token은 쿠키에 담아 보낸다
  2. Access Token가 유효하지 않을 때 쿠키에 담아서 같이 보낸 Refresh Token 값으로 검증 후 정상일 경우 Access Token을 재발급하고 요청한 HTTP통신을 처리하며 Access Token값을 클라이언트에 Response Header로 보낸다
    • 조회 버튼 눌렀는데 발급만 해서 보내면 다시 또 조회버튼을 눌러야하니 사용자가 불편할 수도 있으니 이렇게 처리한다. 또한 HTTPS통신을 사용하면 Response Header값이 암호화 되어서 중간자 탈취에도 안전하다
  3. 클라이언트에서는 Fetcher의 Response에 Access Token키의 값이 왔을 때 Redux에 저장한다

 

Refresh Token 만료

Refresh Token이 만료된 경우 일반적으로 보안상의 이유로 사용자에게 재로그인을 요청하는 것이 일반적인 접근입니다.

재로그인을 통해 새로운 Access Token과 Refresh Token을 발급받는 것이 안전합니다.

  1. 클라이언트에서 Access Token이 만료되었을 때 쿠키에 담아서 보낸 Refresh Token에 대한 검증을 한다. 정상일 경우 Access Token을 재발급하지만 비정상인 경우 다시 로그인 하게끔 해야한다.
  2. 서버에서는 Cookie에 담긴 Refresh Token을 지운다.
  3. HTTPS통신으로 Header에 Refresh Token이 만료 되었다는 값을 담아서 보낸다.
    • HTTPS통신이라 암호화가 되어서 중간 탈취에 안전하다.
  4. 클라이언트는 Fetcher를 통해 Header에 만료되었다는 값이 들어오면 체크해서 Redux에 있는 Access Token을 초기화시키고 Login페이지로 이동시킨다.

 

자동 로그인

  1. 로그인 여부가 true이면 아래 내용 진행 (쿠키 저장을 브라우저 영역으로 저장한다)
  2. 페이지 처음 들어오면 Refresh Token이 존재하는 경우 같이 보낸다
  3. 서버에서 Refresh Token이 정상인 경우 Access Token 만료 시나리오를 따라간
  4. 서버에서 Refresh Token이 비정상인 경우 Refresh Token 만료 시나리오를 따라간다

 

새로고침시 로그인 유효 / 로그인 후 사용 가능한 페이지 접근

  1. 로그인 여부가 true이면 아래 내용 진행 (자동 로그인이 아니면 로그인시 세션 영역으로 만들어짐)
  2. 페이지 처음 들어오면 Refresh Token이 존재하는 경우 같이 보낸다
  3. 서버에서 Refresh Token이 정상인 경우 Access Token 만료 시나리오를 따라간다
  4. 서버에서 Refresh Token이 비정상인 경우 Refresh Token 만료 시나리오를 따라간다

 

 

로그아웃 (버튼)

  1. 서버에서 Refresh Token값이 있는 쿠키를 삭제한다.
  2. 클라이언트에서 메모리에 저장된 Access Token 삭제한다.
  3. 로그인 페이지로 이동시킨다.

 

강제 로그아웃

  1. 클라이언트는 항상 Refresh Token값이 정상인지랑 DB값하고 같은지 체크해야한다
  2. 블랙리스트 방식도 존재하지만 시간이 지나면 쓸모 없는 데이터(어차피 유효기간 지나서 인증 안 되는 JWT 등…)을 주기적으로 삭제해야하는 번거로움이 있기 때문에 선택 안 함
  3. 인메모리를 사용하면 더 효율적이긴하다
  • Token으로 클라이언트에게 주체가 넘어간 경우 서버에서 토큰을 폐기시킬 수 있는 방법은 쿠키로 보낸 Refresh Token값하고 DB에 저장된 Refresh Token값하고 같은지 체크해야한다
  • 로그인시 Redis 같은 인메모리DB에 token 정보를 저장하고, 로그아웃시 Redis에서 token 정보를 지움으로써 로그아웃 여부를 확인할 수 있다

 

소셜로그인

passport.js로 구현한다

  1. passport docs에 있는대로 처리하고 eamil정보를 받아서 따로 회원가입을 진행시키던가 강제로 회원가입 시켜서 회원정보정도는 관리해야한다
  2. 소셜로그인을 클릭한 이후에 동작은 로그인 프로세스와 동일하게 한다
    • 서버에서 Access Token, Refresh Token 토큰 발행
    • 서버에서 Refresh Token DB 저장
    • 서버에서 Response 쿠키에 Http Only 사용상태로 리프레시 토큰 저장 후 클라이언트로 보내기
    • 서버에서 클라이언트로 Access Token 값 보낸후 클라이언트에서 메모리(Redux) 저장

 

Access Token 탈취

  • 메모리에 저장하기 때문에 사용자가 직접 주지 않는 이상 탈취되진 않는다.
  • 기간을 짧게 잡아서 탈취되더라도 못 쓰게한다
  • 메모리에 담기 때문에 XSS나 CSRF 방지가 가능하다
  • HTTPS를 통해 패킷 탈취되어도 암호화가 되어서 안 보이게 된다

 

Refresh Token 탈취

  • 사용자는 리프레시 토큰을 쿠키에 저장해서 보낸다. (Http only, Secure 보안 사항 등록)
  • 리프레시 토큰은 DB에 저장한다
    • JWT 장점은 클라이언트가 인증 권한을 관리해서 서버에서 로그아웃 시킬 수 있는 방법이 없는데 DB나 Redis에 Refresh Token값을 저장하고 Refresh Token 검증시 저장값 하고 다르면 로그아웃시켜버리게 한다

 

XSS 방어로 쿠키 탈취 막기

  • XSS 방어 관련된 라이브러리 (Express의 Helmet 등…)
  • 웹 방화벽
  • Cookie의 Http Only 사용

 

CSRF 막기

  • SameSite설정 → 브라우저 호환 문제 발생으로 사용하기 힘듬
  • 쿠키에 인가 정보 넣지 않기 → 메모리 보관 (Private 변수, Redux)

 

 

🔗 참고 및 출처

https://inpa.tistory.com/entry/AXIOS-📚-CORS-쿠키-전송withCredentials-옵션

https://velog.io/@cada/토근-기반-인증에서-bearer는-무엇일까

https://velog.io/@hahan/JWT란-무엇인가

https://sokdak-sokdak.tistory.com/11

반응형
반응형

📝평문 저장

  • 비밀번호를 그대로 저장
  • 구현이 간단하지만 보안에 매우 취약하다

 

📝단순 해싱

  • 해시 함수를 이용해 해시값으로 변환하여 저장
  • 평문에 비해 보안이 향상되지만 무차별 대입 공격에 취약하며 동일한 비밀번호가 동일 해시값을 가짐으로 레인보우 테이블 공격에 취약하다

 

📝솔팅

  • 비밀번호에 무작위 값(Salt)을 추가한 후 해시 함수로 변환
  • 동일 비밀번호도 서로 다른 해시 값을 가져 레인보우 테이블 공격 방지 가능하지만 빠른 해시 함수는 무차별 대입 공격에 취약하다

 

📝키 스트레칭

  • 비밀번호에 무작위 값을 추가한 이후 해시 함수를 여러번 실행시킨다
  • 주로 CPU 사용량을 증가시켜 해시 계산 속도를 늦추는 데 중점을 둡니다
  • 최근 일반적 장비로는 1초에 50억개 이상의 해시값을 비교할 수 있지만 스트레칭을 적용하면 장비에서 1초에 5번정도만 비교가 가능하다. 즉, 무차별 대입 공격에 강하지만 나도 저장해야하기 때문에 서버에 부담이 된다.
    • PBKDF2 (Password-Based Key Derivation Function 2)
      • 비밀번호와 솔트를 결합한 후 여러 번 반복하여 해시 값을 생성합니다.
    • bcrypt
      • 키 스트레칭을 구현하며, 비교적 느리게 설계되어 공격자가 비밀번호를 추측하는 속도를 늦춥니다.

📝메모리 집약적 함수

  • 메모리 집약적 함수는 해시 계산 과정에서 많은 메모리 및 CPU를 사용하도록 설계되어, 하드웨어 가속 공격에 대한 저항력을 높입니다.
    • Argon2
      • 비밀번호 해시를 생성하는 최신 알고리즘으로, 메모리와 연산 시간을 조절할 수 있으며, 다양한 공격에 대해 강력한 보안을 제공합니다.
    • scrypt
      • bcrypt와 유사하지만, 메모리 사용량을 증가시켜 더욱 강력한 보안을 제공합니다.

 

반응형