경희대학교 컴퓨터공학부 하계 리턴 백엔드(스프링부트) 스터디 7주차 - 트랙장 최현영

  1. 들어가기 앞서

    1. 웹 페이지를 구축하다 보면, 인증과 인가와 같은 보안 기능 요소를 추가해야 할 때가 있다.
      1. 왜냐하면, 고유한 권한이 부여된[인가] 사용자가 게시글을 작성할 경우가 생기거나, 사용자의 정보를 수정하기위해서는 자신이 맞는지 확인하는 절차[인증]이 필요하기 때문이다.
    2. 스프링에서는 보안을 적용할 때, 스프링 시큐리티를 대개 사용하곤 한다.
    3. 우리는 stateless한 REST API를 지금까지 구현해왔기 때문에, 세션/쿠키와 같은 일반적인 인증과 인가 방식이 아닌, 매 요청마다 토큰값[JWT]를 활용하는 방식에 대해 살펴볼 것이다.
  2. 인증[Authentication]

    1. 사용자가 누구인지 확인하는 절차 ⇒ “죄송한데, 누구세요?”를 물어보는 절차
    2. 아이디와 패스워드를 입력하여 이전에 회원가입된 정보를 바탕으로 일치 여부를 판단한 뒤, 권한을 부여[JWT를 부여]하는 과정을 말함
      1. 만일, 로그인에 실패한다면 인증에 실패한 것임
  3. 인가[Authorization]

    1. 앞서 진행한 인증이 성공했다면 JWT를 받을텐데, 발급받은 JWT를 가지고 로그인 된 사용자만 접근할 수 있는 리소스라면 요청이 정상적으로 보내진다.
    2. 허나, 인증만 받았다고 해서, 개나소나 다 허용해주면 안된다. 왜냐하면 어드민 페이지는 오직 어드민 권한만 가진 사용자만 접근해야하나, 일반 유저도 접근이 가능하다면 매우 치명적인 결과를 초래하기 때문이다.
    3. 따라서, 인가는 인증을 통해 검증된 사용자가 서버 내부의 리소스에 접근하고자 할 때, 그 리소스에 접근할 권리를 지니는지 확인하는 절차를 의미한다. ⇒ “근데, 너 혹시 뭐 돼?”를 물어보는 절차
  4. 주체[Principle]

    1. 리소스에 접근하거나, 해당 api를 요청하고자하는 주체를 의미함
    2. 인증 과정을 통해서 접근 주체[principle]이 신뢰할 수 있는지 확인하고, 인가 과정을 통해 주체에게 부여된 권한을 확인하는 시퀀스를 지님
  5. JWT[Json Web Token]

    1. 클라이언트와 서버간에 정보를 JSON으로 안전하게 통신하기 위한 토큰이다.
    2. URL로 이용할 수 있는 문자열로 구성되어 있으며, signature[전사 서명]가 존재해 신뢰할 수 있다.
    3. JWT는 인가[Authorization]을 하기 위해 사용함.
    4. 구조
      1. xxxxx.yyyyyy.zzzz 형식의 String 타입

      2. xxxxx : Header[헤더]

        1. 검증과 관련된 내용을 담고 있다.
        2. 해싱 알고리즘과 토큰 타입을 나타낸다
        {   
        		"alg": "HS256",//해싱 알고리즘 
        		"typ": "JWT" //토큰 타입
        }
        
      3. yyyyy : Payload[내용]

        1. 토큰에 담을 데이터를 JSON형식으로 나타냄
        2. 토큰에 담기는 key값을 Claim이라고 하는데, 등록된 클레임, 공개 클레임, 비공개 클레임 총 3개로 나뉜다.
        3. 여기서 등록된 클레임을 유심히 살펴볼 필요가 있다. 이미 정의된 속성인데, 총 7가지가 정의되어 있다.
        • iss: 토큰 발급자 주체 (issuer)
        • sub: 토큰 제목 (subject)
        • aud: 토큰을 발급받을 대상자 (audience)
        • exp: 토큰의 만료시간 (expiraton) → 시간으로 설정된다.
        • nbf: Not Before 를 의미하는데, exp와 마찬가지로 시간으로 설정되는데, 설정한 날짜가 지나기 전까지는 토큰이 유효하지 않는다. → 사용 불가 상태에 만든다는 것
        • iat: 토큰이 발급된 시간 (issued at)
        • jti: JWT의 고유 식별자[Unique ID]로서, 중복을 방지하고자 고유한 토큰임을 증명하기 위해 사용됨

        ⇒ 여기서 어떤 것을 Payload에 담을지는 Optional이다.

      4. zzzzz : Signature[서명]

        1. 데이터가 수정되었는지를 판단하기 위해 존재.
        2. 개발자가 임의로 지정한 Secret Key과 Header와 Payload를 인코딩 한 것을 조합하여 Header에 지정한 해싱 알고리즘을 사용하여 서명을 생성함
  6. 스프링 시큐리티

    1. 스프링 시큐리티는 스프링 기반 애플리케이션의 인증과 권한을 담당하는 스프링의 하위 프레임워크이다.
    2. 스프링 시큐리티는 서블릿 필터[Servlet Filter]를 기반으로 동작한다.
    3. 우선, 필터가 무엇인지 살펴보자.
  7. 서블릿 필터[Filter]

    스크린샷 2023-09-12 오후 6.10.47.png

    1. 앞전에 1주차에서 살펴봤던 서블릿의 내용을 상기시켜 보자

      스크린샷 2023-09-10 오후 5.54.26.png

    1. 그렇다면 필터는 뭐냐?

      1. 필터는 서블릿에 도달하기 전에 HTTP 요청에 대한 필터링을 수행하는, 서블릿에서 정의된 인터페이스

      2. 즉, 서블릿 컨테이너가 생성한 HttpServletRequest에는 클라이언트가 요청한 내용이 담겨져 있는데, 서블릿에 전달하기 전 우선적으로 검사하여 필터링[여과] 하겠다는 것이다.

      3. 개발자가 직접 필터를 생성하여 요청에 대한 필터링을 수행할 수 있는데, 일반적으로 Filter 인터페이스를 상속받아 구현한다.

        public class CustomFilter implements Filter {

        }

      4. Filter 인터페이스는 3가지 메소드를 지니고 있다.

        1. init() : 스프링이 실행되고, 필터가 처음 생성될 때 수행
        2. doFilter() : HttpServletRequest와 HttpServletResponse가 필터를 거칠 때 수행
        3. destroy() : 스프링이 종료되고, Filter가 소멸할 때 수행
      5. 실습

        스크린샷 2023-09-10 오후 8.54.09.png

        • 새 패키지 생성해주자

        스크린샷 2023-09-10 오후 8.55.22.png

        • Filter 인터페이스는 jakarta.servlet 패키지에 구성되어있다.

        스크린샷 2023-09-10 오후 8.55.29.png

        • 세 메서드를 오버라이딩 해주자.
        package com.rebe.returnstudy.Configuration;
        
        import jakarta.servlet.*;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.stereotype.Component;
        
        import java.io.IOException;
        
        @Slf4j
        @Component
        public class CustomFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                Filter.super.init(filterConfig);
                log.info("Return Study Custom 필터 생성");
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                log.info("현재, Custom Filter 통과중");
            }
        
            @Override
            public void destroy() {
                Filter.super.destroy();
                log.info("Return Study Custom 필터 소멸");
            }
        }
        
      • 여기서 ServletRequest/Reponse 객체를 입력 파라미터로 받는데, 이는 HttpServletRequest가 상속을 받고 있기 때문이다. ⇒ 다형성을 활용한 것.(polymorphism)

        스크린샷 2023-09-10 오후 8.56.22.png

      • 필터를 사용하기 위해서는 빈으로 등록해야한다. 필터를 등록하기 위해서는 FilterRegistrationBean으로 설정해 주어야한다.

      package com.rebe.returnstudy.Configuration;
      
      import jakarta.servlet.Filter;
      import lombok.RequiredArgsConstructor;
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @RequiredArgsConstructor
      @Configuration
      public class Config {
      
          private final CustomFilter customFilter;
      
          @Bean
          public FilterRegistrationBean<Filter> firstFilterRegister() {
              FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
              registrationBean.setFilter(customFilter); //필터 등록
              return registrationBean;
          }
      
      }
      
      • 실행 하였다면, 빌드 시점에 “Return Study Custom 필터 생성” 이라는 log문이 찍히는 것을 볼 수 있다. 즉, 필터는 스프링 컨테이너가 관리하는 빈으로 등록되었고, WAS가 실행[빌드]되었을 때, init()이라는 메서드가 호출되며 필터가 Dispatcher Servlet 앞에 놓여진 것이다.

        스크린샷 2023-09-10 오후 9.17.47.png

      • 어떤 요청을 보내도, 필터를 반드시 통과하게 되어, “현재 Custom Filter를 통과중”이라는 로그문이 출력된 모습을 볼 수 있다. → 즉, 필터를 통과할 때, doFilter() 메서드가 호출된 것이다.

        스크린샷 2023-09-10 오후 9.20.55.png

      • 스프링 빌드를 종료할 때[8080 포트의 PID를 죽일때] “Return Study Custom 필터 소멸이라는 로그문이 출력되며 동적으로 생성한 필터가 소멸되었다. → 종료될 때, destory() 메서드가 호출되었다.

        스크린샷 2023-09-10 오후 9.22.08.png

    스크린샷 2023-09-12 오후 6.11.08.png

  8. 필터 체인[Filter Chain]

    1. 필터체인은 서블릿 컨테이너에서 관리하는 일련의 필터들이라고 보면 된다.
    2. 필터체인을 ApplicationFilterChain이라고도 하는데, → **ApplicationFilterChain**은 필터 체인을 구성하고 필터들 사이에서 요청과 응답을 전달하는 역할을 담당한다.
      1. 서블릿 컨테이너’가 관리하는 필터 체인임을 유의.
      2. 스프링 컨테이너가 관리하지 않음을 유의.
    3. 여러개의 서블릿 필터들은 필터 체인(Filter Chain)을 통해 연결되며, 각 필터는 요청과 응답을 처리하고 다음 필터로 이를 전달한다.
  9. DelegatingFilterProxy

스크린샷 2023-09-11 오전 1.19.09.png

  1. SecurityFilter Chain

⇒ 정리해 보자면, 스프링 시큐리티가 제공하는 필터들을 바탕으로 SecurityFilter Chain으로 구성한 뒤, Deligating Proxy 내부에 존재하는 FilterChainProxy가 이 Security Filter Chain을 ‘스프링 빈’으로 등록해서 스프링 컨테이너가 사용할 수 있도록 해줌

  1. Security Filter Chain 종류들
    1. 스프링 시큐리티에서 제공하는 필터들은 다음과 같다.
    2. 순차적으로 요청이 필터체인을 통과한다.
    3. 요런게 있다 정도만 이해하고 넘어가자
      1. SecurityContextPersistenceFilter와 UsernamePasswordAuthenticationFilter는 매우 중요하니, 어떤 역할을 하는지 집중해서 보자.

스크린샷 2023-09-12 오후 6.11.24.png

⇒ 즉, SecurityContext를 가져오거나 저장하는 역할을 한다.