TIL - 0522

스프링부트

스프링 인터셉터 - 로그인 확인

  • 로그인 기능이 들어가는 기능마다 세션 확인 로직을 구현하거나 각 컨트롤러마다 별도 메소드로 빼고 사용하는 방식으로 개발해야하는데, 각각 구현해두지않고 별도로 구현해두고 요청이 들어왔을 때 거치도록 하는 방법이 있음
  • 방법 : 인터셉터, 필터
  • 공통적으로 처리해야하는 로직을 구현해두고, 공통적으로 처리되도록 하는 기능

  • 필터와 인터셉터의 차이
    • 필터 : J2EE에서 정의된 기능, Dispatcher Servlet(아래 설명)의 앞단에서 처리(스프링 컨텍스트 외부, 스프링과 무관한 자원에 대해 동작)
    • 인터셉터 : 스프링 프레임워크에서 자체적으로 제공하는 기능, Dispatcher Servlet에서 컨트롤러로 가기 전에 정보를 처리(스프링 컨텍스트 내부 - Dispatcher Servlet부터, 스프링 빈 객체에 접근이 가능함)

  • Dispatcher Servlet : HTTP 요청을 처리함, 중앙처리기(front controller pattern), 각각 역할에 요청 - 응답을 받아 응답을 완성해서 클라이언트에 응답해주는 역할
    1. 클라이언트 요청
    2. 맵핑된 컨트롤러 요청
    3. 컨트롤러 리턴
    4. 컨트롤러에 요청 : 비즈니스 로직(메소드)
    5. 요청에 대한 처리(응답) 리턴
    6. 뷰 파일명 요청
    7. 뷰 리턴
    8. 스프링 모델에 처리 데이터를 담아 뷰에 전달
    9. 뷰 만들기 후 리턴
    10. 클라이언트에 응답
  • 인터셉터 메소드
    1. preHandle : 컨트롤러 실행 직전 동작
    2. postHandle : 컨트롤러 진입 후 view 랜더링 전 수행
    3. afterComplete : 뷰 정상적으로 랜더링 된 후 가장 마지막 실행
    4. afterConCurrentHandlingStarted : 비동기 요청 시 postHandle, afterComplete 대신 실행(요청이 완료된 후)
  • 로그인 확인에 적용하기
/* 인터셉터 정의 */
@Service
public class SessionInterceptor implements HandlerInterceptor {
	private static final Logger log = LoggerFactory.getLogger(SessionInterceptor.class);
		
	@Override
	public boolean preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		HttpSession session = request.getSession();
		if (!HttpSession.isLogin(session)) {
			log.info("require login");
			response.sendRedirect("/users/loginForm");
			return false;
		}
		return true;
	}
}

/* MvcConfig 사용 설정 */
public class WebMvcConfig implements WebConfigurer {
	
	@Override
	public void addInterceptor(InterceptorRegistry registry) {
		registry.addInterceptor(new SessionInterceptor())
				 .addPathPatterns("/**")
				 .excludePathPatterns("/")
				 .excludePathPatterns("/users/login**");
	}
}
  • @Service : 빈 등록
  • HandlerInterceptor : 인터셉터(핸들러) 표준 인터페이스(이전에는 HandlerInterceptorAdator를 상속했음 - 어댑터 패턴을 적용하여 모든 메소드를 구현하지않아도 되도록 Adapter 클래스를 만들어둠 하지만 자바8부터 default 메소드를 인터페이스에서 정의할 수 있으므로, 인터페이스의 모든 메소드를 구현하지않아도 되기때문에 HandlerInterceptor를 직접 구현하면 됨 - API도 그렇게 업데이트되어있음)
  • 세션에서 로그인 속성이 있는지 체크한 후 없다면 redirect 메세지 응답 -> 재요청 하도록 함
  • 인터셉터 적용 URI와 적용하지않을 URI 패턴을 WebMvcConfig에서 지정함

예외처리하기(전역)

  • 처리 이전에 생각해볼 것 : 예외발생은 어디서?
    • 예외가 발생했다는 것은 로직을 수행했다는 것 : 보통 로직은 도메인에서 처리를 함, 도메인에서 예외가 발생
    • 예외 처리 : 예외 처리는 컨트롤러에서 처리하도록 역할 분리
  • 컨트롤러에서 예외처리하기
    • 개별 컨트롤러 처리
      • 방법1 : 해당 컨트롤러 액션(메소드)에서 try ~ catch로 예외처리
      • 방법 2 : 에러 처리 전문 컨트롤러로 데이터를 스프링모델에 담아 넘기고 표시해주면서 예외처리
    • 전역 범위로 처리 : @ControllerAdvice 를 통해 컨트롤러에서 발생하는 예외를 한 곳에서 처리할 수 있도록 설정할 수 있음, 응집도를 높여서 중복 줄이면서 변경부분 최소화
  • 전역 범위로 처리하기
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ErrorController {
	private static final Logger log = LoggerFactory.getLogger(ErrorController.class);
	
	@ExceptionHandler(UnAuthorized.class)
	public ResponseEntity<?> handleUnAuthorizeRequest(UnAuthorizedException e) {
		return new ResponseEntity<>("권한이 없습니다.", HttpStatus.UNAUTHORIZED);
	}
}
  • @ControllerAdvice : 예외 전역 처리를 위한 빈 등록
  • @Order : @COntrollerAdvice 빈이 여러개 일 경우 우선순위를 정하기위한 어노테이션
  • 에러 코드를 그대로 클라이언트에 응답해주지않고, 원하는 메세지를 응답코드(401)와 함께 응답함

리눅스

  • 리눅스 파일시스템은 리눅스 파일 시스템 표준 계층 구조에 의해 설계됨 : 볼륨의 구조

링크

  • 하드 링크 : 유닉스의 링크 생성 방식, 디렉토리와 다른 파일시스템 파일 참조 불가, 어떤 파일에 대한 링크인지 표시되지않음(inode number 비교로 알 수 있음 - 식별자, 첫번째 값), 링크 파일이 삭제되더라도 파일이 그대로 있음(파일 복사인데, 링크가 걸려있는 것 - 원본 삭제 되더라도 같은 파일이라서 삭제와 상관없이 계속 사용)
292825 -rw-rw-r-- 2 ubuntu ubuntu    0  5월 22 03:00 target-hard
292825 -rw-rw-r-- 2 ubuntu ubuntu    0  5월 22 03:00 target.log
  • 심볼릭 링크 : 참조될 파일이나 디렉토리를 가리키는 포인터가 포함된 파일을 생성함(위치정보, 윈도우 바로가기와 매우 흡사), 어떤 것을 참조하고 있는지 보임, 링크 대상 파일이 삭제되더라도 심볼릭 링크는 살아있지만 링크는 깨진 상태(NULL), 상대경로로 지정해주는 것이 좋음(링크 대상 파일의 이름 변경, 이동을 허용함)
292671 lrwxrwxrwx 1 ubuntu ubuntu    6  5월 22 03:04 target-sym -> target
292812 drwxrwxr-x 2 ubuntu ubuntu 4096  5월 22 02:59 target

명령어

  • 쉘에 내장된 명령어거나 프로그램(실행 프로그램)이거나 사용자 지정 별칭(alias)임
  • type 명령어를 통해서 해당 명령어가 어떤 타입인지 알 수 있음
$ type cd
cd is a shell builtin # build-in

$ type zsh
zsh is /usr/bin/zsh # program

$ type c
c is an alias for clear # alias
  • 여러 명령어 한번에 실행하기 : ;
cd hello; vi hello.html; 
  • 명령어
* cp
* mkdir
* mv
* rm
* ln
* type
* man
* apropos(적절한 명령어찾기)
* whatis
* alias / unalias(터미널 세션 종료되면 없어짐 - 쉘프로그램 설정파일에 alias 설정해두면 쉘 시작될 때마다 alias 설정되도록 할 수 있음)

네트워크 : 데이터 송수신 가능상태로 만들기

connect 하자

  • 소켓을 만들었다고 해서 소켓에는 아무런 정보가 담겨있지않음 : 통신을 위한 소켓을 만들고 메모리에 기록해둔 것, 네트워크 어플리케이션만 정보를 알고 있음
  • 서버측 입장에서도 소켓을 만들었다고해서 클라이언트의 정보(IP, 포트 등)를 알 수 없음, 서버측 프로그램 또한 정보를 미리 알 수 없음
  • connect를 하기위해선 네트워크 어플리케이션에서 클라이언트 측 프로토콜 스택에 제어정보를 전달하고, 프로토콜 스택이 서버측 프로토콜 스택에 제어정보를 알려야함
  • 제어정보를 알린 후 유효한 통신이라면 통신 대상 기록을 소켓에 기록하고, 버퍼메모리를 만드는 작업을 connect 한다고 말함

제어정보란?

  • 통신하기위한 기본 정보
  • 제어정보는 수신측에 전달하기위한 용도(TCP 프로토콜 사양에 의해 내용이 정해짐), 로컬 프로토콜 스택을 제어 용도
  • 수신측에 전달되는 정보 : 송수신측 IP와 포트, 통신 제어를 위한 정보(컨트롤비트 - 제어 상태를 알리기위한 비트 정보, 체크섬 - 오류를 위한 것 등)이 있음
  • 프로토콜 스택을 제어하기위한 정보 : 소켓에 기록됨(수신측은 알 수 없음 - OS에 따라 필요한 정보가 다름), 통신 요청 IP(+포트), 수신 IP(+포트), 통신 상태, PID

수신 측(서버)에 제어정보 전달하기

  1. 네트워크 어플리케이션이 클라이언트 프로토콜 스택에 정보를 전달
  2. 프로토콜 스택이 TCP 담당에 프로토콜 사양에 맞춰 헤더(TCP 헤더)를 작성 : SYN이라는 컨트롤비트를 1로 만듦(송신측과 수신측의 연결을 확인하는 비트, 1로 되어있어야함)
  3. IP 담당으로 보내서 패킷을 전송
  4. 서버 측 프로토콜 스택의 IP 담당이 패킷을 받아 TCP 담당에 전달
  5. 처리 후 똑같이 TCP 프로토콜 사양에 맞춰 패킷 헤더 생성 : SYN, ACK(수신이 되었다는 표시) 1로 만듦
  6. 클라이언트 프로토콜 스택 IP -> TCP로 도착 : SYN 부분이 1일 경우 통신에 성공했다는 뜻이므로 서버의 정보를 소켓에 기록함

connect 완료

  • TCP 헤더에 의해(정확히는 컨트롤비트 SYN == 1) 통신이 정상적임을 알았으니 소켓에 서버 정보를 기록하여 통신할 때 해당 소켓을 활용해서 통신할 수 있도록 함
  • 실제로 파이프가 존재하지는 않지만 데이터 통신을 할 수 있는 파이프가 형성된 것으로 봄 : 서로가 확인되었기때문에
  • 데이터 송수신 가능한 상태가 되었음