반응형
**JWT(Json Web Token)**는 안전하고 효율적인 인증 및 권한 부여를 제공하는 데 가장 널리 사용되는 기술 중 하나입니다. 스프링 부트 3는 JWT와의 통합을 손쉽게 처리할 수 있도록 다양한 도구와 유틸리티를 제공합니다. 이 글에서는 JWT의 기본 개념, 스프링 부트 3와의 통합 방법, 그리고 실제 애플리케이션에서의 구현 예제를 다룹니다.
1. JWT란 무엇인가?
JWT는 JSON 포맷을 기반으로 하여 정보를 안전하게 교환하기 위한 토큰입니다. 일반적으로 클라이언트와 서버 간 인증 및 데이터 전송에 사용됩니다.
JWT의 구조:
JWT는 점(.)으로 구분된 3개의 파트로 구성됩니다.
- Header (헤더)
- JWT의 타입과 서명 알고리즘 정보가 포함됩니다.
- { "alg": "HS256", "typ": "JWT" }
- Payload (페이로드)
- 사용자 정보와 클레임(Claim)이 담깁니다.
- { "sub": "user123", "name": "John Doe", "roles": ["USER"] }
- Signature (서명)
- 헤더와 페이로드를 기반으로 서버의 비밀키로 생성된 서명입니다. 이를 통해 데이터의 무결성을 보장합니다.
2. JWT를 사용하는 이유
- 상태를 유지하지 않는 인증
서버가 세션을 저장하지 않고도 클라이언트를 인증할 수 있습니다. - 빠른 인증 처리
클라이언트가 매 요청 시 토큰을 보내면 서버는 토큰의 서명만 검증하여 사용자 인증을 처리합니다. - 확장성
JWT는 다양한 클라이언트(웹, 모바일 등)에서 사용될 수 있습니다.
3. 스프링 부트 3와 JWT 통합하기
(1) 프로젝트 설정
먼저 Maven 또는 Gradle에 필요한 의존성을 추가합니다.
Maven
<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>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
Gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
(2) JWT 생성 및 검증 유틸리티 클래스 작성
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
private static final String SECRET_KEY = "mySecretKey123456789012345678901234567890"; // 256-bit key
private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 1시간
private Key getSigningKey() {
return Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
}
// JWT 생성
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// JWT 검증
public Claims validateToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
}
(3) Security 설정
스프링 부트 3에서 JWT를 인증 필터로 사용하려면 SecurityFilterChain을 설정합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않음
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
(4) JWT 필터 작성
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
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 chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
try {
String username = jwtUtil.validateToken(token).getSubject();
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(username, null, null));
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
chain.doFilter(request, response);
}
}
(5) 로그인 및 인증 API 작성
Controller
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
public AuthController(JwtUtil jwtUtil, PasswordEncoder passwordEncoder) {
this.jwtUtil = jwtUtil;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/login")
public String login(@RequestBody AuthRequest request) {
// 실제로는 데이터베이스에서 사용자 인증
if ("user".equals(request.getUsername()) && "password".equals(request.getPassword())) {
return jwtUtil.generateToken(request.getUsername());
}
throw new RuntimeException("Invalid credentials");
}
}
class AuthRequest {
private String username;
private String password;
// Getters and Setters
}
4. 실제 요청 흐름 예제
- 로그인 요청
- 요청:
POST /auth/login Content-Type: application/json { "username": "user", "password": "password" }
- 응답:
HTTP/1.1 200 OK { "token": "eyJhbGciOiJIUzI1NiIsInR..." }
- 요청:
- 인증된 API 호출
- 요청:
GET /api/resource Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR...
- 응답:
HTTP/1.1 200 OK { "data": "Secure Data" }
- 요청:
5. 마무리 및 추가 팁
스프링 부트 3와 JWT의 통합은 인증과 권한 부여를 효율적으로 처리할 수 있는 강력한 방법입니다. 위에서 소개한 예제와 함께 JWT를 활용하여 확장 가능한 보안 시스템을 구축해 보세요.
다음 단계로는 JWT와 Spring Security의 권한 관리 통합, Refresh Token 구현, 그리고 OAuth2와의 결합
반응형
'스프링 부트3' 카테고리의 다른 글
스프링 부트 3의 AOP 기본 사용법 (1) | 2024.12.04 |
---|---|
스프링 부트 3에서 Java Config 활용하기 (0) | 2024.12.04 |
Spring Boot 3의 기본 보안 설정 (0) | 2024.12.04 |
OAuth2를 사용한 스프링 부트 3 인증 구현 (0) | 2024.12.04 |
스프링 부트 3에서 파일 업로드 구현 (1) | 2024.12.04 |