(시큐어코딩)Spring Boot에서 JWT 기반 인증 보안 구현하기
로그인 보안, 제대로 구현하고 계신가요? 쿠키 대신 토큰 기반 인증이 대세입니다. Spring Boot에서 JWT로 안전한 인증 체계를 만들어보세요!
안녕하세요, 보안에 강한 개발 문화를 만드는 ICT리더 리치 블로그입니다. 오늘은 Java Spring Boot 환경에서 가장 많이 사용되는 인증 방식 중 하나인 JWT(Json Web Token)를 활용한 인증 보안 구현에 대해 자세히 알려드리려 합니다. 시큐어코딩의 관점에서도 JWT는 세션 기반 인증보다 훨씬 유연하고 확장성이 높으며, 마이크로서비스 구조에도 매우 적합합니다. 이 글을 통해 실무에서도 바로 적용 가능한 JWT 인증 방식의 전체 흐름을 이해하고, 보안 코딩 요소도 함께 체크해보세요!
📌 바로가기 목차
| "JWT 기반 인증을 상징하는 대표 이미지 – Spring Boot 보안 블로그용 썸네일" |
1. 왜 JWT인가? 인증 방식 비교
기존 웹 애플리케이션에서 많이 사용되던 세션 기반 인증은 서버에 인증 상태를 저장해야 하므로, 확장성과 유지보수에 한계가 있었습니다. 반면 JWT는 클라이언트에 인증 정보를 담은 토큰을 발급하고, 서버는 이를 검증만 하면 되기 때문에 서버 상태를 유지하지 않아도 됩니다.
JWT는 특히 SPA(Single Page Application), 모바일 앱, 마이크로서비스 환경에서 효과적이며, 서버 간 인증 처리에도 이상적입니다. JSON 포맷을 사용하여 유연하고, 필요한 정보만을 포함시켜 사용 가능합니다.
2. JWT 인증 전체 흐름 한눈에 보기
JWT 인증은 다음과 같은 단계로 이루어집니다. 아래의 표는 전반적인 흐름을 정리한 것입니다.
| 단계 | 설명 |
|---|---|
| 1. 로그인 요청 | 사용자가 아이디/비밀번호를 전송 |
| 2. 토큰 발급 | 서버에서 JWT 토큰 생성 후 응답 |
| 3. 인증된 요청 | 사용자는 토큰을 Authorization 헤더에 포함하여 요청 |
| 4. 토큰 검증 | 서버는 JWT의 유효성과 서명을 확인하고, 요청 처리 |
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 요청 헤더에서 Authorization 추출
String header = request.getHeader("Authorization");
// 2. 토큰이 없거나 "Bearer "로 시작하지 않으면 필터 통과
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
// 3. JWT 토큰 파싱
String token = header.substring(7);
// 4. 토큰 유효성 검사
if (!jwtUtil.validateToken(token)) {
filterChain.doFilter(request, response);
return;
}
// 5. 사용자 정보 추출
String username = jwtUtil.getUsernameFromToken(token);
// 6. Spring Security의 UserDetails 조회
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 7. 인증 객체 생성 및 SecurityContext 등록
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
// 8. 다음 필터로 넘김
filterChain.doFilter(request, response);
}
}
이 필터는 Spring Security의 OncePerRequestFilter를 상속받아 모든 요청마다 실행되며,
JWT 토큰을 추출하여 검증하고, 검증이 완료되면 SecurityContext에 인증 정보를 등록합니다.
Authorization 헤더 → JWT 파싱 → 유효성 검증 → 사용자 인증 등록이라는 JWT 인증의 핵심 흐름을 실무적으로 보여주는 코드입니다.
3. JWT 토큰의 구조와 암호화 방식
JWT는 총 3개의 파트로 구성되어 있습니다. 각각은 base64로 인코딩되어 점(.)으로 구분됩니다.
- Header: 알고리즘(HMAC, RSA)과 타입(JWT) 정의
- Payload: 사용자 정보 및 클레임 데이터 포함
- Signature: 서버의 비밀 키를 이용해 서명
JWT는 대칭(HMAC) 또는 비대칭(RSA, ECDSA) 암호화를 지원하며, 무결성과 신뢰성을 확보합니다. 단, Payload는 누구나 읽을 수 있기 때문에 민감 정보는 절대 포함하지 않아야 합니다.
4. Spring Boot에서 JWT 인증 구현 방법
Spring Boot에서 JWT 기반 인증을 구현하려면 Spring Security 필터 체인을 구성하고, JWT를 생성/검증하는 로직을 작성해야 합니다. 일반적인 구성은 다음과 같습니다.
| 구성요소 | 역할 |
|---|---|
| JWTUtil | 토큰 생성 및 파싱, 서명 검증 기능 제공 |
| JwtFilter | Request에서 JWT 추출 및 SecurityContext에 저장 |
| SecurityConfig | Spring Security 설정 및 필터 등록 |
이외에도 로그인 API에서 JWT를 발급하고, 이후 요청에서 이를 Authorization 헤더에 포함시키는 구조가 일반적입니다. CORS, 예외처리, 인증 예외 설정도 함께 고려해야 합니다.
5. 시큐어코딩 체크리스트 – 보안 실수 방지
JWT 인증 시스템을 구축할 때 자주 발생하는 실수를 미연에 방지하기 위해 다음 체크리스트를 따라야 합니다.
- 민감한 정보(비밀번호, 카드정보 등)는 JWT에 절대 포함하지 말 것
- 토큰 서명 키는 환경변수 또는 보안 Vault에 저장
- 토큰 유효기간(exp) 반드시 설정
- HTTPS를 통해서만 토큰을 주고받도록 강제
- 클라이언트 측 로컬스토리지 사용 시 XSS 방지 필수
![]() |
| "JWT 인증의 구조와 흐름을 시각적으로 한눈에! Spring Boot 보안의 핵심 요소를 전문가처럼 이해해보세요." |
6. 토큰 만료, 재발급, 리프레시 토큰 전략
Access Token은 보안상 짧은 시간만 유효하도록 설정하는 것이 안전합니다. 그러나 사용자가 불편하지 않도록, 보통 리프레시 토큰 전략을 함께 사용합니다.
리프레시 토큰은 DB 또는 Redis에 저장하여 관리하며, 사용자가 Access Token이 만료되었을 때 새로운 토큰을 재발급 받을 수 있도록 합니다. 이를 통해 인증 세션을 유지하면서도 보안을 강화할 수 있습니다.
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final JwtUtil jwtUtil;
private final RefreshTokenService refreshTokenService;
public AuthController(JwtUtil jwtUtil, RefreshTokenService refreshTokenService) {
this.jwtUtil = jwtUtil;
this.refreshTokenService = refreshTokenService;
}
/**
* AccessToken 재발급을 위한 API
* 클라이언트가 유효한 RefreshToken을 함께 전송해야 함
*/
@PostMapping("/refresh")
public ResponseEntity<TokenResponse> refresh(@RequestBody TokenRequest request) {
String refreshToken = request.getRefreshToken();
// RefreshToken 유효성 검증
if (!jwtUtil.validateToken(refreshToken)) {
throw new CustomSecurityException("유효하지 않은 리프레시 토큰입니다.");
}
// 서버 저장소에서 토큰 존재 여부 확인
String userId = jwtUtil.getUserIdFromToken(refreshToken);
if (!refreshTokenService.exists(userId, refreshToken)) {
throw new CustomSecurityException("등록되지 않은 리프레시 토큰입니다.");
}
// 새로운 AccessToken 발급
String newAccessToken = jwtUtil.generateAccessToken(userId);
return ResponseEntity.ok(new TokenResponse(newAccessToken));
}
}
이 코드는 사용자의 Refresh Token이 유효한 경우, 새로운 Access Token을 발급하여 반환하는 대표적인 로직입니다.
서버에 저장된 Refresh Token 존재 여부 확인 → 유효성 체크 → 새로운 토큰 발급 순으로 보안적으로 안전하게 처리됩니다.
CustomSecurityException 은 별도 정의한 예외 처리 클래스로, 상황에 따라 적절한 응답을 구성해야 합니다.
7. 자주 묻는 질문 (FAQ)
네. JWT는 중간에 탈취될 위험이 있기 때문에, 반드시 HTTPS 환경에서만 통신해야 안전합니다.
아닙니다. Payload는 Base64로 인코딩될 뿐, 암호화되지는 않기 때문에 누구나 내용을 볼 수 있습니다. 민감 정보를 넣으면 안 됩니다.
서버 측에서 DB나 Redis 같은 안전한 저장소에 저장하고, 사용자가 요청 시 유효성을 확인하는 방식이 일반적입니다.
가능합니다. 다만 XSS 및 CSRF에 노출되지 않도록 HttpOnly 및 Secure 속성을 반드시 설정해야 합니다.
소셜 로그인(Google, Kakao 등)이나 권한 위임이 필요한 경우에는 JWT만으로는 부족하며, OAuth2를 함께 사용하는 것이 좋습니다.
8. 마무리 요약
✅ JWT 인증은 단순하지만 강력한 보안 메커니즘입니다
Spring Boot에서의 JWT 인증은 API 중심 시스템에 가장 적합한 인증 방식 중 하나입니다. 불필요한 세션 관리를 없애고, 마이크로서비스 및 모바일 연동에도 뛰어난 효율을 보여줍니다. 다만, 모든 보안 기술과 마찬가지로 올바른 사용법과 시큐어코딩 원칙이 반드시 병행되어야 진정한 보안 효과를 기대할 수 있습니다. 이 글을 통해 JWT 인증의 핵심 개념부터 실전 적용까지 체계적으로 이해하셨기를 바랍니다. 지금 바로 여러분의 서비스에 보안 강화를 시작해보세요!

댓글
댓글 쓰기