FROM openjdk:11 # 컴파일 할 jdk 버전
WORKDIR /usr/src/app # 모든 작업 파일 기준 위치
ARG JAR_PATH=./build/libs # 변수 사용하듯이 선언해서 사용
COPY ${JAR_PATH}/IPGeoCheck-0.0.1-SNAPSHOT.jar ${JAR_PATH}/IPGeoCheck-0.0.1-SNAPSHOT.jar
# 빌드한 jar 파일을 도커 컨터이너 내부로 옮겨주는 작업
CMD ["java","-jar","./build/libs/IPGeoCheck-0.0.1-SNAPSHOT.jar"]
# jar 파일 실행 명령
3. 도커 이미지 빌드
- 터미널에서 아래와 같이 명령어 실행(도커 파일이 있는 위치에서)
docker build . -t springbootapp (도커 이미지 빌드)
4. 도커 이미지 실행
- p 옵션을 넣어서 실행한다.
도커 내부 네트워크와 외부 네트워크를 연결하기 위한 포트 연결(로컬 포트 / 도커 내부 포트)
주의 : 포트 옵션을 주지 않으면 도커 이미지를 실행 되었으나 접근이 안되는 현상이 발생된다.
docker run -p 8080:8080 springbootapp(이미지 실행)
※ 문제점 : 어플리케이션 코드의 변경으로 인해서 도커 이미지를 매번 다시 빌드해서 실행해야 한다.
이러한 불편함을 해소하고자 Volume 옵션을 사용한다.
5. 볼륨(Volume) 옵션을 이용한 도커 이미지 실행
docker run -p 8080:8080 -v $(pwd):/usr/src/app springbootapp(볼륨을 사용한 이미지 실행)
# -v "local 참조할 경로" : "참조할 도커 이미지 경로"
Volume 옵션은 실행에 필요한 파일을들 컨테이너 내부에서 참조할 수 있도록 해줍니다.
로컬 경로에 존재하는 모든 파일들을 도커 컨테이너 내부에서 사용할 수 있습니다.
SecurityContext를 HTTP session에 캐시(기본 전략)하여 여러 요청에서 Authentication을 공유할 수 있는 공유 필터
SecurityContextRepository를 교체하여 세션을 HTTP session이 아닌 다른 곳에 저장하는 것도 가능하다.
같은 session에서만 공유된다.
스프링 시큐리티 Filter와 FilterChainProxy
스프링 시큐리티 필터는 FilterChainProxy가 호출한다.
여기에 등록되는 필터들은 SecurityConfig에서 설정한 정보가 SecurityFilterChain을 만드는데 사용된다.(FilterChainProxy.getFilters에 SecurityFilterChain)
SecurityConfig 설정에 따라서 등록되는 Filter에 개수가 달라진다.
DelegatingFilterProxy와 FilterChainProxy
DelegatingFilterProxy
일반적인 서블릿 필터(위에서 살펴본 다른 필터들과 같은 서블릿 필터지만 서블릿에 직접 등록되는 필터)
서블릿 필터 처리를 스프링에 들어있는 빈으로 위이함고 싶을 때 사용하는 서블릿 필터
타겟 빈 이름을 설정한다.
스프링 부트 없이 스프링 시큐리티 설정할 때는 AbstractSecurityWebApplicationInitializer를 사용해서 등록
스프링 부트를 사용할 때는 자동으로 등록된다. (SecurityFilterAutoConfiguration)
FilterChainProxy
보통 "springSecurityFilterChain" 이라는 이름의 빈으로 등록된다.
DelegatingFilterProxy
AccessDecisionManager
Access Control 결정을 내리는 인터페이스로, 구현체 3가지를 기본적으로 제공
AffirmativeBased : 여러 Voter중에 한명이라도 허용하면 허용, 기본 전략
ConsensusBased : 다수결
UnanimousBased : 만장일치
AccessDecisionVoter
해당 Authentication이 특정한 Object에 접근할 때 필요한 ConfigAttributes를 만족하는지 확인한다.
WebExpressionVoter : 웹 시큐리티에서 사용하는 기본 구현체, ROLE_Xxxx가 매치하는지 확인
RoleHierarchyVoter : 계층형 ROLE 지원, ADMIN > MANAGER > USER
FilterSecurityInterceptor
AccessDecisionManager를 사용하여 Access Control또는 예외 처리하는 필터.
대부분의 경우 FilterChainProxy에 제일 마지막 필터로 들어있다.
ExceptionTranslationFilter
AuthenticationException
AuthenticationEntryPoint 실행
AbstractSecurityInterceptor 하위 클래스(예, FilterSecurityInterceptor)에서 발생하는 예외만 처리
그렇다면 UsernamePasswordAuthenticationFilter에서 발생한 인증 에러는? UsernamePasswordAuthenticationFilter 자체에서 처리한다.
AccessDeniedException
익명 사용자라면 AuthenticationEntryPoint 실행
익명 사용자가 아니라면 AccessDeniedHandler에게 위임
정리
DeligatingFilterProxy -> FilterChaninProxy -> 시큐리티 필터 목록들(체인들은 어떻게 만들어지는가? WebSecurity, HttpSecurity를 이용해서 만들어진다. 참고 - WebSecurity 주석) -> 인증 관련된 객체(AuthenticationManager) -> 인가 관련된 객체(AccessDecisionManager) -> SecurityContextHolder -> SecurityContext -> Authentication -> Pricipal, GrantAuthority
@Slf4jpublicclassDecoratorPatternTest{
@TestvoidnoDecorator(){
Component realComponent = new RealComponent();
DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
client.execute();
}
}
결과 : RealComponent 실행
result=data
MessageDecorator.java
- 데코레이터 패턴을 이용해서 메시지 꾸며주는 기능 추가
- MessageDecorator는 Component 인터페이스를 구현한다.
@Slf4jpublicclassMessageDecoratorimplementsComponent{
private Component component;
publicMessageDecorator(Component component){
this.component = component;
}
@Overridepublic String operation(){
log.info("MessageDecorator 실행");
String result = component.operation();
String decoResult = "<< =====" + result + " ===== >>";
log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후 ={}",result, decoResult);
return decoResult;
}
}
DecoratorPatterTest.java
- 클라이언트가 호출하기 전에 MessageDecorator 클래스를 이용해서 부가 기능을 추가 후 클라이언트에게 messageDecorator를 넘겨준다. messageDecorator도 Component를 구현하고 있기 때문에 클라이언트 입장에서는 전혀 문제가 발생하지 않는다.
@Slf4jpublicclassDecoratorPatternTest{
@Testvoiddecorator1(){
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
client.execute();
}
}
결과 :
정리 :
데코레이터 패턴의 의도 : 객체에 추가 책임(기능)을 동적으로 추가하고, 기능 확장을 위한 유연한 대안 제공
탬플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿을 두고, 변하는 부분을 자식 클래스에 두어서 상속을 사용해서 문제를 해결하였습니다. 전략 패턴은 변하지 않는 부분을 Context 라는 곳에 두고, 변하는 부분을 Strategy 라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결합니다.
상속이 아니라 위임으로 문제를 해결하는 것이다.
전략 패턴에서 Context 는 변하지 않는 템플릿 역할을 하고, Strategy 는 변하는 알고리즘 역할을 합니다.
전략 패턴 의도
알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자.
전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있습니다.
코드
방식1. 필드에 전략을 보관하는 방식
ContextV1.java
@Slf4jpublicclassContextV1{
private Strategy strategy; // 전략을 필드 변수로 선언publicContextV1(Strategy strategy){
this.strategy = strategy;
}
publicvoidexecute(){
long startTime = System.currentTimeMillis();
//비지니스 로직 실행
strategy.call(); //위임//비즈니스 로직 종료long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
@Slf4jpublicclassContextV1Test{
@TestvoidstrategyV1(){
StrategyLogin1 strategyLogin1 = new StrategyLogin1();
ContextV1 contextV1 = new ContextV1(strategyLogin1);
contextV1.execute();
StrategyLogin2 strategyLogin2 = new StrategyLogin2();
ContextV1 contextV2 = new ContextV1(strategyLogin2);
contextV2.execute();
}
}
전략 패턴 사용은 선 조립, 후 실행으로 생각하면 보다 간단합니다.
변하지 않는 코드 ContextV1 객체에 변하는 알고리즘인 StrategyLogin1, StrategyLogin2를 생성자에 넣어서 조립하고, 호출이 필요한 시점에 execute()를 이용해서 실행합니다. 스프링 애플리케이션 개발 할 때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 다음에 실제 요청을 처리하는 것과 같은 원리이다.
위와 같이 사용하게 되면 변하는 알고리즘인 전략 class가(StrategyLogin1,StrategyLogin2) 계속 생겨날 수밖에 없습니다.
이러한 점을 보완하기 위해서 익명 내부 클래스를 사용합니다.
익명 내부 클래스 사용
@Slf4jpublicclassContextV1Test{
@TestvoidstrategyV2(){
Strategy strategyLogic1 = new Strategy() {
@Overridepublicvoidcall(){
log.info("비즈니스 로직1 실행");
}
};
ContextV1 contextV1 = new ContextV1(strategyLogic1);
contextV1.execute();
Strategy strategyLogic2 = new Strategy() {
@Overridepublicvoidcall(){
log.info("비즈니스 로직2 실행");
}
};
ContextV1 contextV2 = new ContextV1(strategyLogic2);
contextV2.execute();
}
}