- Timeout: Beyond a certain wait interval, a successful result is unlikely or simply too late. - Retry: Many faults are transient and may self-correct after a short delay – use exponential backoff and jitter to avoid cascading failures. - Circuit Breaker: When a system is seriously struggling, failing fast is better than making clients wait to avoid running out of critical resources. - Fallback: Things will still fail – plan what you will do when that happens. - Throttling: Prevent misbehaving clients bringing your service down. We prepare for rapid growth with ongoing load-tests:ts bringing your service down. - Idempotence: Multiple identical requests made to a service apply only once. - Recoverable: Your service has a process to replay messages to recover from an outage of a downstream service. - Dead-letter queues: Erroneous messages do not stop a service processing valid messages and are published to a dead-letter queue for later analysis.
양자화란 연속적으로 보이는 양을 자연수로 셀 수 있는 양으로 재해석하는 과정이라고 한다. 기본적으로 용량이나 계산량을 줄이는 것이다. 현실에서 예를 들면, 현실의 연속적으로 보이는 순간을 비디오로 찍으면 비디오 찍으면 비디오의 픽셀 단위로 각 색상을 인식해 영상화 한다. 그 과정에서 연속적인 현실의 모습이 자연수로 셀 수 있는 양으로 양자화되어 비디오로 만들어진다.
기본적으로, 데이터 손실에 해당한다. 그렇지만 우리의 머니와 시간은 한정되어 있기 때문에, 연산량과 저장량을 줄이기 위해 필수적으로 수행해야한다.
양자화 코드 예시
import yaml
from furiosa.models.vision import SSDMobileNet
from furiosa.quantizer import quantize
from furiosa.runtime.sync import create_runner
image = ["image path"]
mobilenet = SSDMobileNet()
onnx_model = mobileNet.origin
calib_range = mobileNet.tensor_name_to_range
quantized_onnx = quantize(onnx_model, calib_range)
with create_runner(quantized_onnx, devic="warbox*1") as runner:
inputs, contexts = mobilenet.preprocess(image)
outputs = runner.run(inputs)
mobilenet.postprocess(outputs, contexts[0])
CPU와 GPU는 둘 다 데이터를 읽어들여 연산처리를 통해 답을 도출하는 기능을 수행합니다. 컴퓨터에서의 두뇌 역할을 한다는 점에서는 비슷합니다. 다만, 프로세서 내부의 구조를 살펴보면 CPU와 GPU는 차이가 큽니다.
CPU와 GPU 같은 프로세서 내부는 크게 연산을 담당하는 산출연산처리장치(ALU)와 명령어를 해석하고 실행하는 컨트롤유닛(CU), 각종 데이터를 담아두는 캐시로 나뉘게 됩니다.
CPU
ALU 이외에도 맥락을 저장할 수 있도록 뇌의 형태를 카피한 형태로 되어 있다. 기본적으로 DRAM과 결합하여 컴퓨팅을 수행할 수 있도록 구성되어 있다. 일반적으로 ALU (core) 의 수와 DRAM 혹은 cache 의 성능과 비례한다. 계산만을 수행하기 위한 기기는 아니고, 전체 job을 컨트롤 하며 인간이 이해할 수 있는 프로그래밍 언어를 컴퓨터의 언어 (0101010 이진수로 된 명령어)로 풀어내서 수행하는 연산을 내부적으로 수행한다. 대부분의 작업은 순차적으로 이루어진다.
GPU
ALU 머신이다. 계산을 병렬적으로 하기 위해 설계 되었다. 캐시메모리를 두어 계산하는 형태보다는 연산만을 위한 기기이다.
고정소수점(fixed point): C#에서 decimal가 고정소수점 방식. 정밀한 표현이 가능하지만 표현할 수 있는 범위가 적습니다.
부동소수점(floating point): C#에서 float, double가 부동소수점 방식. 훨씬 더 넓은 범위까지 표현할 수 있지만 오차가 발생합니다.
CPU보다 GPU를 사용해야 하는 경우 (AWS 설명)
CPU와 그래픽 처리 장치(GPU) 사이에서 선택할 때 서로 배타적이지 않다는 점에 유의해야 합니다. 클라우드의 모든 서버 또는 서버 인스턴스를 실행하려면 CPU가 필요합니다. 그러나 일부 서버에는 GPU를 추가 코프로세서로 포함하기도 합니다. 특정 워크로드는 특정 함수를 보다 효율적으로 수행하는 GPU가 있는 서버에서 실행하는 것이 더 적합합니다. 예를 들어, GPU는 부동 소수점 숫자 계산, 그래픽 처리 또는 데이터 패턴 매칭에 유용할 수 있습니다.
다음은 CPU보다 GPU를 사용하는 것이 더 유용한 몇 가지 분야입니다.
딥 러닝
딥 러닝은 인간의 두뇌에서 영감을 얻은 방식으로 데이터를 처리하도록 컴퓨터를 가르치는 인공 지능(AI) 방식입니다. 예를 들어, 딥 러닝 알고리즘은 그림, 텍스트, 사운드 및 기타 데이터의 복잡한 패턴을 인식하여 정확한 인사이트와 예측을 생성할 수 있습니다. GPU 기반 서버는 기계 학습, 신경망 및 딥 러닝 작업에 고성능을 제공합니다.
고급 운전자 지원 시스템(ADAS)과 자율 주행 교통 수단(AV) 시스템을 개발하고 배포하려면 확장성이 뛰어난 컴퓨팅, 스토리지, 네트워킹 및 분석 기술이 필요합니다. 예를 들어, 데이터 수집, 레이블링 및 주석, 지도 개발, 알고리즘 개발, 시뮬레이션 및 검증을 위한 기능이 필요합니다. 이러한 복잡한 워크로드에서는 효율적인 연산을 위해 GPU 기반 컴퓨터 시스템의 지원이 필요합니다.
서버(Server) + 리스(Less)의 합성어라 간혹 '서버가 없다'라고 문자 그대로 이해할 수 있지만, 절대 그렇지 않다.
서버리스(Serverless)는 클라우드 컴퓨팅의 모델 중 하나로 개발자가 서버를 직접 관리할 필요가 없는 아키텍처를 의미한다.
예를들어, 서버의 사용자가 1000명이 될 걸 예상하고 그에 맞는 용량의 서비스를 구입했다면 실제 사용자가 1000명이든 0명이든 같은 금액을 내야 할 것이다. 이는 크던 작던 손실을 일으키기 마련이다.
하지만 서버리스는 동적으로 서버의 자원을 할당한다.
사용자가 없다면 자원을 할당하지 않고 대기하다 요청이 들어오면 그 때 자원을 할당해서 요청을 처리하고 다시 대기 상태로 들어가게 된다. 즉, 자원을 효율적으로 사용할 수 있는 것이다. 비용 또한 대기상태를 제외한 실제 사용 자원에 대해서만 청구되기 때문에 굉장히 경제적이며, 해당 서버는 클라우드 제공 기업에서 전적으로 관리하기 때문에 개발자는 스케일링, 업데이트, 백업, 보안 등 서버에 대해 일절 관리하거나 신경 쓸 필요가 없어 비즈니스 로직에 집중하여 개발을 할 수 있다.
서버리스 아키텍처(Serverless Architecture)의 구현 방식
서버리스 아키텍처의 대표적인 두 가지 구현 방식은 다음과 같다.
FaaS (Function as a Service) : AWS Lambda, Microsoft Azure Function, Google Cloud Functions ... 등 BaaS (Backend as a Service) : Firebase, Kinvey, Parse ... 등 두 가지 구현 방식으로 나뉘지만, 일반적으로 서버리스라고 하면 FaaS를 가리킨다.
FaaS(Function as a Service)
FaaS는 Function 즉, 함수를 서비스로 제공한다.
사용자가 작성한 코드(백엔드)를 서버리스 제공자의 서버에 업로드하면 해당 서버는 업로드한 코드를 함수 단위로 쪼개 대기상태로 두게된다. 그러다 요청이 들어오면 서버가 대기상태에 두었던 함수를 실행시켜 처리한 후 작업이 끝나면 다시 대기상태로 만드는 구조이다. 비용은 함수 호출 횟수에 따라 청구된다.
쉽게 말해 업로드한 코드는 평소에 자고있고, 요청이 들어오면 서버가 코드를 깨워 요청을 처리한 후 다시 재운다.
FaaS의 특징
Stateless
FaaS 서비스는 함수가 실행되는 동안에만 관련된 자원을 할당하게 되며, 함수가 항상 같은 머신에서 실행된다는 보장이 없다. 따라서 함수 실행 시 로컬에서 어떤 상태(State)가 유지될 수 없다. 이를 해결하고 싶다면 메모리에 상태를 저장하지 않고 AWS의 경우 S3을 이용하거나 아예 DB를 사용해야 한다.
Ephemeral
위에서 말한 것과 같이 함수는 특정 이벤트가 발생했을 때만 컨테이너로서 배포되고, 실행이 끝난 후엔 자원이 회수되므로 일시적으로만 배포된다고 할 수 있다.
BaaS (Backend as a Service)
BaaS는 일반적으로 SPA, 안드로이드와 같은 클라이언트 중심으로 개발된 어플리케이션이다.
클라이언트단에서 BaaS가 제공하는 인증, DB, 사용자 관리 등과 같은 백엔드 관련 외부 서비스를 사용해서 대부분의 비즈니스 로직을 처리한다.
쉽게말해 SNS연동이나 DB와 같이 백엔드에 필요한 기능들을 사용자가 직접 구현할 필요 없이 제공하는 API로 해당 기능을 구현할 수 있게 해주는 것이다. 클라우드 공급자가 백엔드 개발 환경까지 제공해준다고 보면 될 것 같다.
누가 BaaS를 사용해야 할까? BaaS 플랫폼은 기술적인 서비스이고, 앱 개발자들을 위해 만들어졌다.
Backend 개발에 있어 한정된 지식을 가진 Frontend 개발자
빠른 개발을 원하는 Backend 개발자
어떤 유형의 프로젝트가 BaaS와 잘 어울릴까?
실시간 응용프로그램(채팅, 메시지 앱)
교통 앱
소셜-네트워크 유형 앱
전자상거래 앱
음악 또는 영상 스트리밍 앱
게임
서버리스(Serverless)의 장단점
장점
기존 IaaS나 PaaS와는 다르게 실제 사용량에 대해서만 비용이 청구되므로 경제적 서버에 신경 쓸 필요가 없으므로 개발자 생산성 향상 및 개발 시간 단축 자동 스케일 업 및 스케일 다운 간단한 패키징 및 배포
단점
Cold Start 서버가 항시 요청에 대기하고 있는게 아니라 느리다. 프로젝트 규모가 작다면 크게 신경쓸만한 사항은 아니지만 규모가 커지거나 속도를 요구하는 프로젝트라면 서버리스는 좋은 선택이 아닐 수 있다.
실행 시간 한계 서버리스는 단순 작업(댓글 쓰기, 이메일 보내기 등)에는 적합하지만 긴 시간이 필요한 작업(동영상 업로드, 데이터 백업 등)에는 굉장히 비효율적이다. 서버리스는 함수가 1회 호출 될 때 사용할 수 있는 메모리 및 시간에 제한이 있기 때문이다. 만약 작업이 끝나지 않은채로 해당 시간이 지나면 작업이 완료될때까지 일정 시간마다 계속 함수를 다시 호출하게 된다. 클라우드 제공 플랫폼에 종속적
디버깅 및 테스트 불편 서버리스(Serverless)를 추천하는 대상 서버리스는 사이드 프로젝트(토이 프로젝트)나 빠르게 시제품(초기 서비스)을 런칭하고 싶은 경우에 추천한다. 쉽고 빠르게 제품을 출시할 수 있으며, 돈도 매우 절약된다.
현대모비스는 우수한 SW 인재 채용을 위해 상시로 채용 설명회를 진행하고 있습니다. 채용 설명회에서는 채용과 관련된 상담을 원하는 참가자에게 멘토와 1:1로 상담할 수 있는 기회를 제공합니다. 채용 설명회에는 멘토 n명이 있으며, 1~k번으로 분류되는 상담 유형이 있습니다. 각 멘토는 k개의 상담 유형 중 하나만 담당할 수 있습니다. 멘토는 자신이 담당하는 유형의 상담만 가능하며, 다른 유형의 상담은 불가능합니다. 멘토는 동시에 참가자 한 명과만 상담 가능하며, 상담 시간은 정확히 참가자가 요청한 시간만큼 걸립니다.
참가자가 상담 요청을 하면 아래와 같은 규칙대로 상담을 진행합니다.
상담을 원하는 참가자가 상담 요청을 했을 때, 참가자의 상담 유형을 담당하는 멘토 중 상담 중이 아닌 멘토와 상담을 시작합니다.
만약 참가자의 상담 유형을 담당하는 멘토가 모두 상담 중이라면, 자신의 차례가 올 때까지 기다립니다.참가자가 기다린 시간은 참가자가 상담 요청했을 때부터 멘토와 상담을 시작할 때까지의 시간입니다.
모든 멘토는 상담이 끝났을 때 자신의 상담 유형의 상담을 받기 위해 기다리고 있는 참가자가 있으면 즉시 상담을 시작합니다. 이때,먼저 상담 요청한 참가자가 우선됩니다.
참가자의 상담 요청 정보가 주어질 때, 참가자가 상담을 요청했을 때부터 상담을 시작하기까지 기다린 시간의 합이 최소가 되도록 각 상담 유형별로 멘토 인원을 정하려 합니다.단, 각 유형별로 멘토 인원이 적어도 한 명 이상이어야 합니다.
예를 들어, 5명의 멘토가 있고 1~3번의 3가지 상담 유형이 있을 때 아래와 같은 참가자의 상담 요청이 있습니다.
참가자의 상담 요청
참가자 번호시각상담 시간상담 유형
1번 참가자10분60분1번 유형
2번 참가자
15분
100분
3번 유형
3번 참가자
20분
30분
1번 유형
4번 참가자
30분
50분
3번 유형
5번 참가자
50분
40분
1번 유형
6번 참가자
60분
30분
2번 유형
7번 참가자
65분
30분
1번 유형
8번 참가자
70분
100분
2번 유형
이때, 멘토 인원을 아래와 같이 정하면, 참가자가 기다린 시간의 합이 25로 최소가 됩니다.
1번 유형2번 유형3번 유형
2명1명2명
1번 유형
1번 유형을 담당하는 멘토가 2명 있습니다.
1번 참가자가 상담 요청했을 때, 멘토#1과 10분~70분 동안 상담을 합니다.
3번 참가자가 상담 요청했을 때, 멘토#2와 20분~50분 동안 상담을 합니다.
5번 참가자가 상담 요청했을 때, 멘토#2와 50분~90분 동안 상담을 합니다.
7번 참가자가 상담 요청했을 때, 모든 멘토가 상담 중이므로 1번 참가자의 상담이 끝날 때까지 5분 동안 기다리고 멘토#1과 70분~100분 동안 상담을 합니다.
2번 유형
2번 유형을 담당하는 멘토가 1명 있습니다.
6번 참가자가 상담 요청했을 때, 멘토와 60분~90분 동안 상담을 합니다.
8번 참가자가 상담 요청했을 때, 모든 멘토가 상담 중이므로 6번 참가자의 상담이 끝날 때까지 20분 동안 기다리고 90분~190분 동안 상담을 합니다.
3번 유형
3번 유형을 담당하는 멘토가 2명 있습니다.
2번 참가자가 상담 요청했을 때, 멘토#1과 15분~115분 동안 상담을 합니다.
4번 참가자가 상담 요청했을 때, 멘토#2와 30분~80분 동안 상담을 합니다.
상담 유형의 수를 나타내는 정수 k, 멘토의 수를 나타내는 정수 n과 참가자의 상담 요청을 담은 2차원 정수 배열 reqs가 매개변수로 주어집니다. 멘토 인원을 적절히 배정했을 때 참가자들이 상담을 받기까지 기다린 시간을 모두 합한 값의 최솟값을 return 하도록 solution 함수를 완성해 주세요.
제한사항
1 ≤ k ≤ 5
k ≤ n ≤ 20
3 ≤ reqs의 길이 ≤ 300
reqs의 원소는 [a, b, c] 형태의 길이가 3인 정수 배열이며, c번 유형의 상담을 원하는 참가자가 a분에 b분 동안의 상담을 요청했음을 의미합니다.
1 ≤ a ≤ 1,000
1 ≤ b ≤ 100
1 ≤ c ≤ k
reqs는 a를 기준으로 오름차순으로 정렬되어 있습니다.
reqs 배열에서 a는 중복되지 않습니다. 즉, 참가자가 상담 요청한 시각은 모두 다릅니다.
각 유형별로 멘토가 배정될 수 있는 모든 경우의 수를 가지고 for문을 돌린다. 중복 조합을 사용하였다.
5명 멘토 /.3개 과제
3 1 1 1 2 2 1 3 1 2 1 1 2 1 2
3번
각 과제에 멘토가 배정된 경우일 때 스케쥴링을 돌린 후 기다린 시간의 총합을 계산한다.
4번
각 과제에 멘토가 배정된 수에 따라 기다린 시간의 총합을 일정하므로 그 값을 재활용할 수 있게 dp map에 저장해서 쓴다.
1번 과제에 1명의 멘토가 붙는 경우 1번 과제에 2명의 멘토가 붙는 경우
요런 값들은 기다린 시간의 총합이 정해져있기 때문에 다음 계산 시에도 사용 가능하다.
풀이 코드
from itertools import combinations_with_replacement
from heapq import heappush, heappop
from collections import deque
dp = {}
def solution(k, n, reqs):
answer = 300 * 1000 + 1
reqs_by_type = [[] for i in range(k+1)]
for req in reqs:
reqs_by_type[req[2]].append(req)
for req in reqs_by_type:
req.sort(key = lambda x: x[0])
if (n == k):
return get_total_waiting_time(reqs_by_type, [1] * (k+1))
# 배정한 거 다 돌려보기
for cwr in combinations_with_replacement(list(range(1, k+1)), n-k):
mentor_counts = [1] * (k+1)
for c in cwr:
mentor_counts[c] += 1
answer = min(answer, get_total_waiting_time(reqs_by_type, mentor_counts))
return answer
def get_total_waiting_time(reqs_by_type, mentor_counts) :
time = 0
for type_num, m in enumerate(mentor_counts):
if type_num == 0 : continue
time += get_waiting_time_by_type(reqs_by_type[type_num], type_num, m)
return time
def get_waiting_time_by_type(reqs, type_num, mentor_count):
if not reqs : return 0
key = (type_num, mentor_count)
if key not in dp:
waiting_time = 0
heap = [(0, i) for i in range(mentor_count)]
reqs = deque(reqs[:])
cur_time = 0
while reqs:
end_time, mentor_idx = heappop(heap)
cur = reqs.popleft()
cur_time = max(cur[0], end_time)
waiting_time += max(0, end_time - cur[0])
heappush(heap, (cur_time + cur[1], mentor_idx))
dp[key] = waiting_time
return dp[key]
- <https://mangkyu.tistory.com/76> 글에서 대부분을 참고했음을 알립니다.
- Spring Security 제대로 이해하기 <https://gngsn.tistory.com/160>
- Spring Security의 권한 부여 처리 흐름 [<https://velog.io/@cjstk3221/Spring-Security의-권한-부여-처리-흐름>](<https://velog.io/@cjstk3221/Spring-Security%EC%9D%98-%EA%B6%8C%ED%95%9C-%EB%B6%80%EC%97%AC-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84>)
- Spring Seucurity 인가 처리 흐름 [<https://velog.io/@penrose_15/Spring-Security의-인가-처리-흐름>](<https://velog.io/@penrose_15/Spring-Security%EC%9D%98-%EC%9D%B8%EA%B0%80-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84>)
해보기
임의의 필터를 만들어서 권한을 부여하고 부여한 권한이 있는 경우에만 특정 API 처리하도록 만들어보기
Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리하고 있다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있다. Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.
이러한 Spring Security의 아키텍쳐는 아래와 같다.
[ 인증(Authorizatoin)과 인가(Authentication) ]
인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차
인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차를 진행하게 되며, 인가 과젱에서 해당 리소스에 대한 접근 권한이 있는지 확인을 하게 된다. Spring Security에서는 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다.
Principal(접근 주체): 보호받는 Resource에 접근하는 대상
Credential(비밀번호): Resource에 접근하는 대상의 비밀번호
2. Spring Security 모듈
[ Spring Security 주요 모듈 ]
Spring Security의 주요 모듈은 아래와 같이 구성되며 각 항목들에 대해서 간단히 살펴보도록 하자.
[ SecurityContextHolder ]
SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프래그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다. SecurityContextHolder는 기본적으로 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 방법과SecurityContextHolder.MODE_THREADLOCAL 방법을 제공한다.
[ SecurityContext ]
Authentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있다.
[ Authentication ]
Authentication는 현재 접근하는 주체의 정보와 권한을 담는 인터페이스이다. Authentication 객체는 Security Context에 저장되며, SecurityContextHolder를 통해 SecurityContext에 접근하고, SecurityContext를 통해 Authentication에 접근할 수 있다.
public interface Authentication extends Principal, Serializable {
// 현재 사용자의 권한 목록을 가져옴
Collection<? extends GrantedAuthority> getAuthorities();
// credentials(주로 비밀번호)을 가져옴Object getCredentials();
Object getDetails();
// Principal 객체를 가져옴.Object getPrincipal();
// 인증 여부를 가져옴boolean isAuthenticated();
// 인증 여부를 설정함void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
[ UsernamePasswordAuthenticationToken ]
UsernamePasswordAuthenticationToken은 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, User의 ID가 Principal 역할을 하고, Password가 Credential의 역할을 한다. UsernamePasswordAuthenticationToken의 첫 번째 생성자는 인증 전의 객체를 생성하고, 두번째 생성자는 인증이 완려된 객체를 생성한다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 주로 사용자의 ID에 해당함private final Object principal;
// 주로 사용자의 PW에 해당함private Object credentials;
// 인증 완료 전의 객체 생성public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// 인증 완료 후의 객체 생성public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);// must use super, as we override
}
}
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}
[ AuthenticationProvider ]
AuthenticationProvider에서는 실제 인증에 대한 부분을 처리하는데, 인증 전의 Authentication객체를 받아서 인증이 완료된 객체를 반환하는 역할을 한다. 아래와 같은 AuthenticationProvider 인터페이스를 구현해서 Custom한 AuthenticationProvider을 작성해서 AuthenticationManager에 등록하면 된다.
public interface AuthenticationProvider {
// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
[ Authentication Manager ]
인증에 대한 부분은 SpringSecurity의 AuthenticatonManager를 통해서 처리하게 되는데, 실질적으로는 AuthenticationManager에 등록된 AuthenticationProvider에 의해 처리된다. 인증이 성공하면 2번째 생성자를 이용해 인증이 성공한(isAuthenticated=true) 객체를 생성하여 Security Context에 저장한다. 그리고 인증 상태를 유지하기 위해 세션에 보관하며, 인증이 실패한 경우에는 AuthenticationException를 발생시킨다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
AuthenticationManager를 implements한 ProviderManager는 실제 인증 과정에 대한 로직을 가지고 있는 AuthenticaionProvider를 List로 가지고 있으며, ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 처리를 한다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
public List<AuthenticationProvider> getProviders() {
return providers;
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.for (AuthenticationProvider provider : getProviders()) {
....
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;
}
....
}
throw lastException;
}
}
위에서 설명한 ProviderManager에 우리가 직접 구현한 CustomAuthenticationProvider를 등록하는 방법은 WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 할 수 있다. WebSecurityConfigurerAdapter의 상위 클래스에서는 AuthenticationManager를 가지고 있기 때문에 우리가 직접 만든 CustomAuthenticationProvider를 등록할 수 있다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
return new CustomAuthenticationProvider();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
}
[ UserDetails ]
인증에 성공하여 생성된 UserDetails 객체는 Authentication객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다. UserDetails 인터페이스를 살펴보면 아래와 같이 정보를 반환하는 메소드를 가지고 있다. UserDetails 인터페이스의 경우 직접 개발한 UserVO 모델에 UserDetails를 implements하여 이를 처리하거나 UserDetailsVO에 UserDetails를 implements하여 처리할 수 있다.
UserDetailsService 인터페이스는 UserDetails 객체를 반환하는 단 하나의 메소드를 가지고 있는데, 일반적으로 이를 구현한 클래스의 내부에 UserRepository를 주입받아 DB와 연결하여 처리한다. UserDetails 인터페이스는 아래와 같다.
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
[ Password Encoding ]
AuthenticationManagerBuilder.userDetailsService().passwordEncoder() 를 통해 패스워드 암호화에 사용될 PasswordEncoder 구현체를 지정할 수 있다.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
[ GrantedAuthority ]
GrantAuthority는 현재 사용자(principal)가 가지고 있는 권한을 의미한다. ROLE_ADMIN나 ROLE_USER와 같이 ROLE_*의 형태로 사용하며, 보통 "roles" 이라고 한다. GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.