
이번에 새로 팀 프로젝트를 진행하면서 비용이 많이 들까봐... 1차 배포 전에는 H2 console 을 사용하는 것으로 협의 되었다. 기존에 로그인 방식을 단순 Spring Security 를 사용해서 DB에 아이디, 비밀번호가 있는지만 확인하고 맞으면 로그인되는 형식으로 만들었었는데, 이전 프로젝트에서 사용했던 방식이기도 하고 해보지 않은 방식을 넣어보자! 하게 되어서 JWT 적용을 하게 되었다.
1. Spring Security + JWT 적용 이후 http://localhost:8080/h2-console 접근 시 403 Forbidden 발생
2. permitAll, shouldNotFilter 모두 설정 모두 했지만 여전히 접근 불가
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
);
http.csrf().disable(); // 전체 disable
http.csrf(csrf -> csrf
.ignoringRequestMatchers("/h2-console/**")
);
http.headers(headers -> headers
.frameOptions().disable()
);
http.headers(headers -> headers
.frameOptions().sameOrigin()
);
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers("/h2-console/**");
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getRequestURI();
return path.startsWith("/h2-console");
}
브라우저나 Postman을 통해 /h2-console 요청 시 다음과 같은 HTML 리디렉션이 나타났다.
<script type="text/javascript">
location.href = 'login.jsp?jsessionid=...';
</script>
하지만 리디렉션 후에도 계속 403 Forbidden 에러가 발생하면서 콘솔 페이지에 진입하지 못했다.
application.properties에 로그 레벨을 추가해서 분석을 시도했고,
logging.level.org.springframework.security=DEBUG
logging.level.com.chingubackend=DEBUG
Securing GET /h2-console
Set SecurityContextHolder to anonymous SecurityContext
Pre-authenticated entry point called. Rejecting access
Securing GET /error
Set SecurityContextHolder to anonymous SecurityContext
Pre-authenticated entry point called. Rejecting access
이런 결과가 나왔다.
AnonymousAuthenticationFilter가 요청을 익명 사용자로 처리 이후 Http403ForbiddenEntryPoint가 호출되며 접근 거부하고 있었고 이것은 JWT 인증이 필터 체인에서 무시되었거나, H2 Console 요청이 필터에서 제외되지 않는다는 것을 유추할 수 있었다.
왜 그런지 찾아보니
H2 Console은 세션 기반 인증 방식을 사용하는 반면, 내가 적용한 인증 방식은 JWT 기반의 Stateless 인증 방식이다. 따라서 H2 Console이 기대하는 세션 기반 인증이 없으면 403이 발생한다는 것을 알 수 있었고 Postman에서 JWT 토큰을 전달하더라도 세션은 생성되지 않기 때문에 인증이 되지 않고 있었다. 😲
.httpBasic();
해당 방법을 설정하니 h2-console 접근이 가능해졌고, 아이디와 비밀번호는 회원 DB에 있는 것으로 인증하면 됐었다. 시큐리티 기본 로그인폼을 사용하려했는데 그래도 되나..? 싶어서 ChatGPT에 물어봤고
| 방법 | 설명 | 추천 여부 |
|---|---|---|
| Postman 또는 cURL 사용 | 직접 POST 요청 보내서 테스트 | ✅ 추천 |
| 프론트엔드에서 로그인 요청 | HTML + JavaScript로 로그인 폼 구현 | ✅ 추천 |
| 기본 로그인 폼 활성화 | formLogin() 추가해서 사용 | ❌ 비추천 (JWT와 충돌 가능) |
웹 브라우저에서는 Postman 또는 프론트엔드 로그인 폼을 사용해서 POST /auth/login 요청을 보내야 한다.
Spring Security 기본 로그인 폼(formLogin())은 JWT 방식과 맞지 않으므로 사용하지 않는 것이 좋다.
라는 답을 받았다.
둘의 차이점을 명확하게 알기 위해 간단하게 정리해둔 내용은
httpBasic() 은 브라우저 기본 제공하는 팝업 로그인창을 사용해서 간단하게 인증하며, H2 Console 같이 세션 없이 인증을 요구하는 서비스에 적합하다.
formLogin() 은 Spring Security가 제공하는 로그인 폼 페이지를 제공하며, 세션 기반 로그인 관리가 필요할 때 쓴다. 따라서 H2 Console 접근 시 리디렉션 문제를 일으킬 수 있다.
이다.
httpBasic()은 브라우저 기본 인증 팝업을 사용하는 Stateless 인증 방식이고, formLogin()은 세션 기반 로그인 폼으로 JWT와 충돌할 수 있다.
H2 콘솔 접근 시 /h2-console/**에 대해 permitAll() 설정과 함께 HTTP Basic 인증을 활성화해야 한다.
CSRF 비활성화와 FrameOptions 설정(sameOrigin() 또는 disable())이 H2 콘솔 정상 작동에 필요하다.
JWT 인증과 세션 인증의 차이로 인해 H2 콘솔 같은 세션 기반 인증을 기대하는 서비스에선 별도 예외 처리가 필요하다.
디버그 로그로 인증 흐름을 분석해 문제를 해결하는 것이 중요하다.