Spring Security + JWT 통합 인증 구현 방법
안녕하세요! 오늘은 많은 개발자분들이 궁금해하는 Spring Security + JWT(JSON Web Token) 통합 인증 구현 방법을 단계별로 정리해보려고 합니다. OAuth2처럼 외부 서비스를 이용하지 않고, 자체 로그인 시스템을 만들고 싶은 경우 JWT는 아주 유용합니다. 그럼, 시작해보겠습니다!
1. 왜 JWT를 사용할까?
JWT는 사용자의 인증 상태를 토큰에 담아 클라이언트와 서버가 주고받는 방식이에요.
서버가 세션을 직접 관리할 필요가 없으니 무상태(stateless) 아키텍처에 딱 맞죠.
- 세션 저장소가 필요 없음 → 확장성(Scalability) 확보
- 모바일, 웹 클라이언트 등 다양한 환경에서 쉽게 사용 가능
- Access Token + Refresh Token 전략으로 보안 강화 가능
2. 프로젝트 의존성 추가
Spring Boot + Security + JWT 구현을 위해 아래 의존성을 추가해줍니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
3. JWT 유틸리티 클래스 만들기
JWT 토큰을 생성하고 검증하는 유틸리티 클래스를 작성해봅시다.
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtil {
private final String SECRET = "mySecretKey123";
private final long EXPIRATION_TIME = 1000 * 60 * 60; // 1시간
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public String getUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
4. JWT 필터 구현하기
요청(Request)이 들어올 때 JWT를 검사하는 필터를 작성해야 합니다.
이 필터는 Spring Security의 OncePerRequestFilter
를 상속받아 구현할 수 있어요.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsername(token);
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(username, null, null);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
5. SecurityConfig 설정
이제 우리가 만든 JWT 필터를 Spring Security 설정에 추가해야 합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtUtil jwtUtil;
public SecurityConfig(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
6. 로그인 & 토큰 발급 API
이제 사용자가 로그인하면 JWT를 발급해주는 컨트롤러를 만들어봅시다.
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
private final JwtUtil jwtUtil;
public AuthController(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
// 실제 구현에서는 DB 검증 로직 필요
if ("user".equals(username) && "1234".equals(password)) {
return jwtUtil.generateToken(username);
}
throw new RuntimeException("Invalid credentials");
}
}
이제 /auth/login
에 요청하면 JWT가 반환되고, 이 토큰을 Authorization 헤더에 담아 다른 API를 호출할 수 있습니다.
7. 마무리
오늘은 Spring Security와 JWT를 결합해 인증 시스템을 구현해보았습니다.
핵심은 JWT 생성 → 요청 시 검증 → SecurityContext에 인증 정보 저장이라는 흐름이에요.
여기서 더 나아가려면 Refresh Token, 토큰 블랙리스트 처리, Role 기반 권한 관리 같은 기능을 붙여볼 수 있습니다.