스프링 JWT 심화 9 : 로그아웃

2024. 12. 22. 17:13·Spring Security/JWT

로그아웃 기능

로그아웃 기능을 통해 추가적인 JWT 탈취 시간을 줄일 수 있다.

  • 로그아웃 버튼 클릭 시
    • 프론트엔드 측 : 로컬 스토리지에 존재하는 Access 토큰 삭제 및 서버 측 로그아웃 경로로 Refresh 토큰 전송
    • 백엔드 측 : 로그아웃 로직을 추가하여 Refresh 토큰을 받아 쿠키 초기화 후 Refresh DB에서 해당 Refresh 토큰 삭제 (모든 계정에서 로그아웃 구현 시 username 기반으로 모든 Refresh 토큰 삭제)

백엔드에서 로그아웃 수행 작업

  1. DB에 저장하고 있는 Refresh 토큰 삭제
  2. Refresh 토큰 쿠키 null로 변경

스프링 시큐리티에서의 로그아웃 구현의 위치

일반적으로 스프링 시큐리티 의존성을 프로젝트에 추가했을 경우 기본 로그아웃 기능이 활성화 된다. 해당 로그아웃을 수행하는 클래스의 위치는 필터 단이다.

org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

 

따라서 우리의 커스텀 필터 또한 시큐리티 필터 단에 구현할 예정이다.


로그아웃 필터 구현

public class CustomLogoutFilter extends GenericFilterBean {

    private final JWTUtil jwtUtil;
    private final RefreshRepository refreshRepository;

    public CustomLogoutFilter(JWTUtil jwtUtil, RefreshRepository refreshRepository) {

        this.jwtUtil = jwtUtil;
        this.refreshRepository = refreshRepository;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        //path and method verify
        String requestUri = request.getRequestURI();
        if (!requestUri.matches("^\\/logout$")) {

            filterChain.doFilter(request, response);
            return;
        }
        String requestMethod = request.getMethod();
        if (!requestMethod.equals("POST")) {

            filterChain.doFilter(request, response);
            return;
        }

        //get refresh token
        String refresh = null;
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {

            if (cookie.getName().equals("refresh")) {

                refresh = cookie.getValue();
            }
        }

        //refresh null check
        if (refresh == null) {

            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        //expired check
        try {
            jwtUtil.isExpired(refresh);
        } catch (ExpiredJwtException e) {

            //response status code
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        // 토큰이 refresh인지 확인 (발급시 페이로드에 명시)
        String category = jwtUtil.getCategory(refresh);
        if (!category.equals("refresh")) {

            //response status code
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        //DB에 저장되어 있는지 확인
        Boolean isExist = refreshRepository.existsByRefresh(refresh);
        if (!isExist) {

            //response status code
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        //로그아웃 진행
        //Refresh 토큰 DB에서 제거
        refreshRepository.deleteByRefresh(refresh);

        //Refresh 토큰 Cookie 값 0
        Cookie cookie = new Cookie("refresh", null);
        cookie.setMaxAge(0);
        cookie.setPath("/");

        response.addCookie(cookie);
        response.setStatus(HttpServletResponse.SC_OK);
    }
}

SecurityConfig 등록

http
        .addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class);

'Spring Security > JWT' 카테고리의 다른 글

REDBOX(헌혈증 기부 시스템) - JWT 정리  (0) 2025.01.16
스프링 JWT 심화 8 : Refresh 토큰 서버 측 저장  (1) 2024.12.22
스프링 JWT 심화 7 : Refresh Rotate  (0) 2024.12.22
스프링 JWT 심화 6 : Refresh로 Access 토큰 재발급  (0) 2024.12.22
스프링 JWT 심화 5 : Access 토큰 필터 : JWTFilter  (0) 2024.12.22
'Spring Security/JWT' 카테고리의 다른 글
  • REDBOX(헌혈증 기부 시스템) - JWT 정리
  • 스프링 JWT 심화 8 : Refresh 토큰 서버 측 저장
  • 스프링 JWT 심화 7 : Refresh Rotate
  • 스프링 JWT 심화 6 : Refresh로 Access 토큰 재발급
jhyngu
jhyngu
취업하자.
    티스토리 홈
    |
  • jhyngu
    jhyngu
    jhyngu
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • Dev (151)
      • Java (2)
      • Spring (51)
      • Spring Security (39)
        • JWT (22)
        • OAuth2 (17)
      • Kotlin (2)
      • React (6)
      • Coding Test (28)
      • DB (0)
      • Git (5)
      • Linux (14)
      • docker (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    mybatis
    MVC
    api client
    JDBC
    JWT
    Linux
    Spring Framework
    Spring Boot
    spring web
    oauth2
    MariaDB
    알고리즘
    git
    OAuth2
    react
    백준
    Spring
    Postman
    JavaScript
    Spring Security
  • hELLO· Designed By정상우.v4.10.3
jhyngu
스프링 JWT 심화 9 : 로그아웃
상단으로

티스토리툴바