로그아웃 기능
로그아웃 버튼 클릭시
- 프론트엔드측: 로컬 스토리지에 존재하는 Access 토큰 삭제 및 Axios Api Client를 이용해서 서버측 로그아웃 경로로 Refresh 토큰 전송
- 백엔드측: 로그아웃 로직을 추가하여 Refresh 토큰을 받아 쿠키 초기화(null) 후 Refresh DB에서 해당 Refresh 토큰을 삭제 (모든 계정에서 로그아웃 구현시 username 기반으로 모든 Refresh 토큰 삭제)
백엔드에서 로그아웃 수행 작업
- DB에 저장하고 있는 Refresh 토큰 삭제
- Refresh 토큰 쿠키 null로 변경
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 servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
//path and method verify
String requestUri = request.getRequestURI();
if (!requestUri.matches("^\\/api\\/account\\/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);
}
}
위 코드를 적용해서 POSTMAN에서 작동해봤는데
deleteByRefresh까진 실행이 되는데 쿠키의 Refresh 토큰이 사라지지 않는 것이다......
문제는 쿠키의 보안 속성이었다..........
쿠키에 Secure 또는 HttpOnly 속성이 설정되어 있으면 삭제할 때도 동일한 속성을 사용해야 한다. 예를 들어서 쿠키가 HttpOnly로 설정되어 있으면, 쿠키를 삭제할 떄도 HttpOnly 속성을 포함시켜야 한다.
cookie.setSecure(true); // 만약 원래 쿠키가 Secure로 설정되었다면
cookie.setHttpOnly(true); // 만약 원래 쿠키가 HttpOnly로 설정되었다면
라고 설정하니 정상적으로 로그아웃 됐다...
나는 어떻게 할거냐면~~~~~~~~~~~~~
로그인
Refresh Token은 쿠키에 담아서
Access Token은 헤더에 담아서 전달한다. 각각 refresh, access라는 이름으로
그리고 두 token을 전달받은 프론트엔드는
Refresh Token을 쿠키에
Access Token을 로컬 스토리지 또는 세션 스토리지에 담는다. (자동 로그인을 위해 로컬 스토리지에 담을 예정)
Access Token을 담아서 Api 요청시
만약 Access Token이 만료되면
프론트엔드 측에서 Refresh Token을 보내면 서버 측에서 Refresh Token과 Access Token을 다시 발급해 프론트엔드 측으로 반환한다.
기존 Refresh Token은 DB에서 삭제하고 새로운 Refresh Token을 저장한다.
로그아웃
로그아웃 버튼 클릭시
프론트엔드 측에서 로컬 스토리지에 존재하는 Access Token 삭제하고
Axios Api Client를 이용해서 서버 측 로그아웃 경로로 Refresh Token을 전송한다.
서버 측에서 Refresh Token을 받으면 쿠키를 null로 초기화하고
DB에서 해당 Refresh Token을 삭제한다.
만약 모든 계정에서 로그아웃 구현시 email기반으로 모든 Refresh 토큰을 삭제한다. (그렇다면 DB에서 email Column에 unique 속성을 삭제해야겠지)
'개인 프로젝트' 카테고리의 다른 글
| API 명세서 (0) | 2024.08.30 |
|---|---|
| Axios Interceptors (0) | 2024.08.17 |
| Refresh Token (0) | 2024.08.13 |
| LoginFilter, JWTFilter, Spring Security 인증 과정 (0) | 2024.08.07 |
| SecurityConfig 수정 전/후 (0) | 2024.08.02 |