Spring Security에서 JWT 적용하기

ollie·2023년 12월 11일
0

Authentication

목록 보기
3/3

배경 🐈

프로젝트에서 로컬 인증을 구현하게 되면서 Spring Security를 이용해 JWT 인증 방식( AccessToken, RefreshToken )을 적용하였는데 그때 그린 구조도입니다.
Spring Security는 유난히 구조가 복잡하고 어렵기 때문에 기본 동작 구조에 대해 확실히 알고 넘어가지 않으면 구현 난이도가 엄청 높아지기 때문에 잘 알고 사용해야 합니다. 모듈 중에 개발 시 주요하게 봐야 할 모듈과 개발해야 할 모듈을 중심으로 정리하였습니다.

배경 지식

인증을 한다고 했을 때, 기본적으로 인증은 인증과 인가로 구분됩니다. 인증과 인가에 대한 정의는 여러 블로그에서 잘 정리되어 있습니다. 구현한다고 했을 때 이렇게 나눌 수 있습니다.
인증( Authentication ) - 로그인, 로그아웃 ( + AccessToken 갱신 )
인가( Authorization ) - 그 밖에 로그인해야 이용 가능한 서비스에 권한 부여

📍 Spring Security 기본 동작 구조

먼저 SpringSecurity가 어떻게 돌아가는지를 파악하고 SpringSecurity에 대해 알아봅시다.
springsecurity 동작구조

  1. 요청 가로채기
    a. AuthenticationFilter 중 UsernamePasswordAuthenticationFilter에서 기본적으로 요청을 가로채서 인증 로직을 먼저 거치도록 동작시킨다.
  2. UsernamePasswordAuthenticationToken 생성
    a. Authentication의 하위 객체인 UsernamePasswordAuthenticationToken이 생성된다.
    b. 이때 생성된 UsernamePasswordAuthenticationToken은 principal(ex 아이디), credentials(ex 비밀번호)만 채워지고, authenticated(인증 상태)는 false다.
    UsernamePasswordAuthenticationToken
  3. UserDetailsService의 구현체에서 사용자 정보 찾아 UserDetail로 반환 (loadUserByUsername)
    a. UserDetails : 사용자 정보 저장용
    (Authentication 객체에 사용자 정보 포함하거나 Jwt 토큰 만들 때 사용자 정보 넣는 용도로 사용)
    b. UsernamePasswordAuthenticationToken : 인증, 인가용
  4. AuthenticationProvider에서 로그인 시 받은 loginID, password와 DB의 loginID, password 비교
  5. 인증 성공 시, UsernamePasswordAuthenticationToken 업데이트
    a. 이때 업데이트된 UsernamePasswordAuthenticationToken은 principal, credentials, authorities(ex 일반 사용자, 관리자)가 포함되며 authenticated는 true이다.
  6. AuthenticationFilter까지 반환되어 SecurityContext에 Authentication 저장
    a. SecurityContextHolder 안에 SecurityContext이 담겨있고,
    그 안에 Authentication가 담겨있는 형태

📌 Spring Security 란?

  • 스프링 시큐리티는 스프링에서 인증과 인가 처리를 도와주기 위한 Spring 프레임워크의 하위 프로젝트
  1. Filter 방식 ( vs Interceptor )
    a. Filter 방식 - HTTP 요청 시에 앞에서 (Dispatcher Servlet 전) 요청 뺏어서 통과 시 요청 처리
    b. Interceptor 방식 - HTTP 요청 시 뒤에서 (Disapatcher Servlet 후 Controller 전) 요청 뺏어서 통과 시 요청 처리
  2. Authentication
    a. 요청 하나(thread)가 생길 때마다 새로운 SecurityContextHolder 공간이 생기고, 그 공간 안에 새 Authentication 객체를 담는 방식을 동작
    b. 성공적으로 인증된 Authentication 객체를 담았다 ❗ - 인증 성공
    c. 인증된 Authentication 객체 담기에 실패했다 😢 - 인증 실패
  3. SecurityContextHolder 위치
    a. TreadLocal : 각 스레드 별로 가지는 저장 공간
    b. 요청 생성(=스레드 생성)될 때마다 스레드에 SecurityContextHolder 생성되어 Authentication 저장
  4. 기본적으로 세션 기반 인증 지원
    a. Spring Security는 기본적으로 세션 기반 인증을 지원합니다.
    b. 허나 요즘 JWT를 통한 토큰 인증을 사용하기 때문에 Spring Security에서도 토큰 기반 인증을 구현할 수 있습니다.
    c. 다만 JWT 토큰을 생성하거나 토큰 유효성 검사를 하는 등의 로직을 추가해야 합니다.

📍Spring Security with JWT - Authentication

JWT를 이용해 인증 처리를 어떻게 하면 좋을지 고민하며 동작 방식에 대해 구조도를 그렸습니다.
Authentication

  1. /login 요청 시, UsernamePasswordAuthenticationFilter를 거칩니다.
  2. UsernamePasswordAuthenticationToken 생성
  3. loadUserByUsername 오버라이드
    a. loginID 바탕으로 사용자 정보 찾은 후, UserDetails에 담아 반환
  4. DaoAuthenticationProvider를 사용해 loginID, Password를 비교하는 구현 로직을 사용합니다.
    a. DaoAuthenticationProvider는 스프링에서 기본적으로 제공하는 AuthenticationProvider의 구현
    b. UserDetails 객체가 반환되면 DaoAuthenticationProvider는 UserDetails와 DB의 정보 비교
  5. UsernamePasswordAuthenticationToken 업데이트
  6. AuthenticationFilter까지 반환되어 SecurityContext에 Authentication 저장
  7. UserDetail 객체를 이용해 AccessToken과 RefreshToken 생성
  8. DB에 RefreshToken 저장
    a. 이때 RDB를 사용할 수도 있고, Redis 같은 DB를 사용할 수도 있습니다.
  9. Cookie나 Body에 AccessToken, RefreshToken 넣어 반환

📍 Spring Security with JWT - Authorization

JWT를 이용해 인가 처리를 어떻게 하면 좋을지 고민하며 동작 방식에 대해 구조도를 그렸습니다.
Authorization

  1. /product/add 요청 시, JwtFilter를 거칩니다.
  2. JWT에서 추출한 정보 바탕으로 UsernamePasswordAuthenticationToken 객체 생성
    a. JWTAuthenticationToken을 사용할 수도 있다.
  3. AccessToken 유효성 검사
  4. SecurityContextHolder 공간 생성 후 Authentication 객체 저장
  5. Authentication 정보를 이용해 사용자 인증, 인가를 수행하고 상품 저장 로직을 수행합니다.

마치며

저는 동작 방식을 위의 구조로 그리면서 확인하니 더 정리가 잘 되었고, 이 구조를 바탕으로 구현하였습니다. 다음에는 실제 구현 코드에 대해 정리하는 시간을 가지도록 하겠습니다.


[참고 자료]

SpringSecurity 공식문서
[SpringBoot] Spring Security란?
Spring Security + JWT로 인증 인가 구현하기

profile
생각하는 개발자가 되겠습니다 💡

0개의 댓글