Book/스프링부트 핵심가이드

스프링 시큐리티(Spring Security)와 JWT

블로그 주인장 2023. 12. 5.

서비스 인증과 인가

보안과 관련된 용어와 개념 및 스프링에 보안을 적용할 때 사용하는 스프링 시큐리티(spring security)를 알아보겠습니다.


보안 용어


인증(authentication)

  • 사용자가 누구인지 확인하는 단계를 의미한다.
  • 인증의 대표적인 예시(ex. 로그인)
    • 로그인은 데이터베이스에 등록된 아이디와 패스워드를 사용자가 입력한 아이디 및 비밀번호를 비교해서 일치 여부를 확인하는 과정이다.
    • 로그인에 성공하면 애플리케이션 서버는 응답으로 사용자에게 토큰(token)을 전달한다.
    • 로그인에 실패하면 사용자는 토큰을 전달받지 못해서 원하는 리소스에 접근할 수 없게 된다.

 

인가(authorization)

  • 인증을 통해 검증된 사용자가 애플리케이션 내부의 리소스에 접근할 때 사용자가 해당 리소스에 접근할 권리가 있는지를 확인하는 과정이다.
  • 인가의 대표적인 예시(ex. 게시판 접근)
    • 로그인한 사용자가 특정 게시판에 접근해서 글을 보려고 하는 경우, 게시판 접근 등급을 확인하여 접근을 허가하거나 거부하는 것이 대표적인 인가의 사례이다.
  • 일반적으로 사용자가 인증 단계에서 발급받은 토큰은 인가 내용을 포함하고 있다.
  • 사용자가 리소스에 접근하면서 토큰을 함께 전달하면 서버는 토큰을 통해 권한 유무 등을 확인하여 인가를 수행한다.

 

접근 주체(principal)

  • 애플리케이션의 기능을 사용하는 주체를 의미한다.
  • 사용자가 될 수도 있고, 디바이스, 시스템 등이 될 수도 있다.
  • 인증 과정을 통해 접근 주체가 신뢰할 수 있는지 확인하고, 인가 과정을 통해 접근 주체에게 부여된 권한을 확인하는 과정 등을 거친다.

 

스프링 시큐리티


스프링 시큐리티의 동작 구조

  • 스프링 시큐리티는 서블릿 필터(Servlet Filter)를 기반으로 동작하며, DispatcherServlet 앞에 필터가 배치되어있다.

서블릿 필터 배치

 

필터체인(FilterChain)

  • 서블릿 컨테이너에서 관리하는 ApplicationFilterChain을 의미한다.
  • 클라이언트에서 애플리케이션으로 요청을 보내면 서블릿 컨테이너는 URI를 확인해서 필터와 서블릿을 매핑한다.
  • 필터체인을 서블릿 컨테이너의 필터 사이에서 동작시키기 위해서 DelegatingFilterProxy를 사용한다.

DelegatingFilterProxy

DelegatingFilterProxy

  • 서블릿 컨테이너의 생명주기와 스프링 애플리케이션 컨텍스트 사이에서 다리 역할을 수행하는 필터 구현체이다.
  • 표준 서블릿 필터를 구현하며, 역할을 위임할 필터체인 프록시(FilterChainProxy)를 내부에 가지고 있다.
  • 필터체인 프록시는 스프링부트의 자동 설정에 의해 자동 생성된다.

 

필터체인 프록시(FilterChainProxy)

  • 스프링 시큐리티에서 제공하는 필터로서, 보안 필터체인(SecurityFilterChain)을 통해 많은 보안 필터(Security Filter)를 사용할 수 있다.
  • 필터체인 프록시에서 사용할 수 있는 보안 필터체인은 List 형식으로 담을 수 있게 설정되어 있어 URI 패턴에 따라 특정 보안 필터체인을 선택해서 사용하게 된다.
  • 보안 필터체인에서 사용하는 필터는 여러 종류가 있으며, 각 필터마다 실행되는 순서가 다르다.

 

WebSecurityConfigurerAdpater 

  • 보안 필터체인은 WebSecurityConfigurerAdpater 클래스를 상속받아 설정할 수 있다.
  • 필터체인 프록시는 여러 보안 필터체인을 가질 수 있는데, 여러 보안 필터체인을 만들기 위해서는 WebSecurityConfigurerAdpater 클래스를 상속받는 클래스를 여러 개 생성하면 된다.
  • WebSecurityConfigurerAdpater 클래스에는 @Order 어노테이션을 통해 우선순위가 지정되어 있는데, 2개 이상의 클래스를 생성했을 때 똑같은 설정으로 우선순위가 100이 설정되어 있으면 예외가 발생한다.
  • 이 때문에, 상속받은 클래스에서 @Order 어노테이션을 지정하여 순서를 정의하는 것이 중요하다.
  • 별도의 설정이 없다면 SecurityFilterChain에서 사용하는 필터 중 UsernamePasswordAuthenticationFilter를 통해 인증을 처리한다.

 

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter 인증 과정

  1. 클라이언트로부터 요청을 받으면 서블릿 필터에서 SecurityFilterChain으로 작업이 위임되고 그중UsernamePasswordAuthenticationFilter에서 인증 처리한다.
  2. AuthenticationFilter는 요청 객체에서 username과 password를 추출해서 토큰을 생성한다.
  3. 그 후, AuthenticationManager에게 토큰을 전달한다. AuthenticationManager는 인터페이스이며, 일반적으로 사용되는 구현체는 ProviderManager이다.
  4. ProviderManager는 인증을 위해 AuthenticationProvider 로 토큰을 전달한다.
  5. AuthenticationProvider 는 토큰의 정보를 UserDetailsService에 전달한다.
  6. UserDetailsService는 전달받은 정보를 통해 데이터베이스에서 일치하는 사용자를 찾아 UserDetails 객체를 생성한다.
  7. 생성된 UserDetails 객체는 AuthenticationProvider로 전달되며, 해당 Provider에서 인증을 수행하고 성공하게 되면서 ProviderManager로 권한을 담은 토큰을 전달한다.
  8. ProviderManager는 검증된 토큰을 AuthenticationFilter로 전달한다.
  9. AuthenticationFilter는 검증된 토큰을 SecurityContextHolder에 있는 SecurityContext에 저장한다.
  10. UsernamePasswordAuthenticationFilter는 접근 권한을 확인하고 인증이 실패할 경우, 로그인 폼이라는 화면을 보내는 역할을 수행한다.

 

JWT(JSON Web Token)


  • 당사자 간에 정보를 JSON 형태로 안전하게 전송하기 위한 토큰이다.
  • URL로 이용할 수 있는 문자열로만 구성되어 있으며, 디지털 서명이 적용되어 있어 신뢰할 수 있다.
  • 주로 서버와의 통신에서 권한 인가를 위해 사용된다.
  • URL에서 사용할 수 있는 문자열로만 구성되어 있기에 HTTP 구성요소 어디든 위치할 수 있다.

 

JWT의 구조

JWT 구조

  1. 헤더(Header)
    • JWT 헤더는 검증과 관련된 내용을 담고 있다.
    • [예시] 헤더
      {
          "alg": "HS256",
          "typ": "JWT"
      }​​
    • alg 속성에서는 해싱 알고리즘을 지정한다. 
    • 해싱 알고리즘
      • 보통 SHA256 또는 RSA 를 사용하며, 토큰을 검증할 때 사용되는 서명 부분에서 사용된다.
      • 위의 예시는 HS256은 'HMACSHA256' 알고리즘을 사용한다는 의미이다.
    • typ 속성에는 토큰의 타입을 지정한다.
    • 완성된 헤더는 Base64Url 형식으로 인코딩되어 사용된다.
  2. 내용(Payload)
    • JWT 내용에는 토큰을 담는 정보를 포함한다.
    • 포함되어 있는 속성들은 클레임(Claim)이라고 하며, 크게 3가지로 분류된다.
      • 등록된 클레임(Registered Claims)
      • 공개 클레임(Public Claims)
      • 비공개 클레임(Private Claims)
    • 등록된 클레임의 정의
      • iss : JWT의 발급자(Issuer) 주체를 나타낸다. iss의 값은 문자열이나 URI를 포함하는 대소문자를 구분하는 문자열이다.
      • sub : JWT의 제목(Subject)
      • aud : JWT의 수신인(Audience)이다. JWT를 처리하려는 각 주체는 해당 값으로 자신을 식별해야하며, 요청을 처리하는 주체가 'aud' 값으로 자신을 식별하지 않으면 JWT는 거부가 된다.
      • exp : JWT의 만료시간(Expiration)이다. 시간은 NumericDate 형식으로 지정해야한다.
      • nbf : 'Not Before'를 의미한다.
      • iat : JWT가 발급된 시간(Issued at)
      • jti : JWT의 식별자(JWT ID)이다. 주로 중복 처리를 방지하기 위해 사용된다.
    • 공개 클레임은 키 값을 마음대로 정의할 수 있다. (충돌되자 않을 이름으로 설정해야한다)
    • 비공개 클레임은 통신 간에 상호 합의되고 등록된 클레임과 공개된 클레임이 아닌 클레임을 의미한다.
      {
          "sub": "payload",
          "exp": "160276402",
          "userId": "test",
          "username": "김철수"
      }
    • Base64Url 형식으로 인코딩되어 사용된다.
  3. 서명(Signature)
    • 헤더, 인코딩된 내용, 비밀키, 헤더의 알고리즘 속성값을 가져와 생성된다.
      HMACSHA256(
          base64UrlEncode(Header) + "." +
          base64UrlEncode(payload), 
          secret
      )
    • 서명은 토큰의 값들을 포함해서 암호화하기 때문에 메시지가 도중에 변경되지 않았는지 확인할 때 사용한다.

 

JWT의 디버거

  • 웹 브라우저에서 해당 링크에 접속하면 JWT를 생성해볼 수 있다. [JWT 디버거 링크]
  • Encoded와 Decoded로 나뉘어있으며, 양측의 내용이 일치하는지 사이트에서 확인할 수도 있다.
  • Decoded 내용을 변경하면 Encoded의 컨텐츠가 자동으로 반영된다.

 

JWT 디버거

 

 

 

반응형

댓글