JWT 기반 인증에서 초기에는 쿠키를 사용해 프론트엔드에서 JWT를 처리했으나, HttpOnly 옵션 활성화로 인해 JavaScript에서 쿠키를 직접 읽을 수 없는 문제가 발생했다. 또한, 기존 인증 흐름 설계상, 쿠키를 활용하는 방식에서도 문제점이 발생했다. 이를 해결하기 위해, JWT를 브라우저의 JavaScript에서 직접 관리하지 않고, 서버에서 요청받은 쿠키를 읽어 처리하는 방식으로 변경했다.
Cookie + JWT 조합을 선택한 이유는 간단하다. 브라우저는 기본적으로 서버에 쿠키를 자동으로 전송한다. 이 기능을 활용해, 전송되는 쿠키에 인증 정보를 담아 서버와 주고받는 방식이 더 효율적이고 간결하기 때문이다.
초기 인증 흐름에서는 JWT를 Authorization 헤더에서 직접 추출해 사용했다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (isWhiteListed(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String jwtToken = extractAccessToken(request);
try {
Authentication authentication = jwtProvider.getAuthentication(jwtToken); // JWT 검증
SecurityContextHolder.getContext().setAuthentication(authentication); // 인증 설정
} catch (Exception e) {
sendJwtExceptionResponse(response, new RuntimeException("인증 실패")); // 인증 실패 시 응답
return;
}
}
filterChain.doFilter(request, response); // 필터 체인 진행
}
HttpOnly 옵션 여부와 무관하게 클라이언트가 JWT를 헤더에 수동으로 넣어야 해서 문제가 계속된다.초기에는 JWT를 쿠키에 저장하고 HttpOnly 옵션을 비활성화하여 JavaScript가 쿠키를 읽고 사용할 수 있도록 했다.
public void addJwtToCookie(HttpServletResponse response, String jwtToken, String cookieName) {
Cookie cookie = new Cookie(cookieName, jwtToken);
cookie.setHttpOnly(false); // JavaScript에서 쿠키 접근 가능
cookie.setSecure(false); // HTTPS 필수 여부 미적용
cookie.setPath("/"); // 쿠키 경로 전체 허용
cookie.setMaxAge(60 * 120); // 쿠키 유효 시간 (2시간)
response.addCookie(cookie);
}
JWT를 HttpOnly 쿠키에 저장하고, 서버가 요청받은 쿠키에서 값을 추출해 인증을 처리하도록 변경했다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (isWhiteListed(request.getRequestURI())) {
filterChain.doFilter(request, response); // 화이트리스트 요청은 인증 건너뛰기
return;
}
String jwtToken = getCookieValueFromToken(request, "accessToken"); // 쿠키에서 JWT 추출
try {
Authentication authentication = jwtProvider.getAuthentication(jwtToken); // JWT 검증
SecurityContextHolder.getContext().setAuthentication(authentication); // 인증 객체 설정
} catch (Exception e) {
sendJwtExceptionResponse(response, new RuntimeException("인증 실패")); // 인증 실패 시 응답
return;
}
String email = jwtProvider.getClaims(jwtToken).getSubject(); // JWT에서 사용자 이메일 추출
request.setAttribute("email", email); // 요청 속성에 사용자 이메일 추가
filterChain.doFilter(request, response); // 다음 필터로 진행
}
getCookieValueFromToken: 쿠키에서 JWT 추출accessToken)의 값을 찾아 반환한다.private String getCookieValueFromToken(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return null; // 쿠키가 없으면 null 반환
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
return cookie.getValue(); // 지정된 이름의 쿠키 값 반환
}
}
return null;
}
jwtProvider.getAuthentication: JWT를 검증하고 인증 객체를 생성한다.sendJwtExceptionResponse: 인증 실패 시, HTTP 응답으로 오류 메시지를 반환한다.JWT 기반 인증에서, 브라우저가 자동으로 서버로 전송하는 쿠키에 인증 정보를 담아 주고받는 방식으로 설계했다.
이로써 보안 강화와 클라이언트 코드의 간결화를 동시에 충족할 수 있다.