프로젝트에 JWT 기능을 추가하기 위해 pom.xml에 다음 의존성을 추가합니다.
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
JWT 유틸리티 클래스
토큰의 생성과 검증을 담당하는 유틸리티 클래스를 작성합니다.
TokenManager
package com.example.security;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class TokenManager {
private static final String SECRET = "mySecretKey2024";
private static final long EXPIRATION_MS = 1000 * 60 * 60 * 12;
public static String createToken(Map<String, Object> payload) {
return JWT.create()
.withClaim("data", payload)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.sign(Algorithm.HMAC256(SECRET));
}
public static Map<String, Object> verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(SECRET))
.build()
.verify(token)
.getClaim("data")
.asMap();
}
}
단위 테스트
테스트 의존성을 추가합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
토큰 생성 테스트
@Test
void generateToken() {
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("userId", 1001);
userInfo.put("nickname", "devUser");
String jwt = JWT.create()
.withClaim("user", userInfo)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))
.sign(Algorithm.HMAC256("mySecretKey2024"));
System.out.println(jwt);
}
토큰 검증 및 파싱 테스트
@Test
void validateToken() {
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJJZCI6MTAwMSwibmlja25hbWUiOiJkZXZVc2VyIn0sImV4cCI6MTcyMTAxMjY5Nn0.xxx";
DecodedJWT decoded = JWT.require(Algorithm.HMAC256("mySecretKey2024"))
.build()
.verify(jwt);
Map<String, Claim> claims = decoded.getClaims();
System.out.println(claims.get("user"));
}
인증 인터셉터 구현
요청 헤더의 토큰을 검사하는 인터셉터를 만듭니다.
AuthInterceptor
package com.example.interceptor;
import com.example.security.TokenManager;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
String bearer = req.getHeader("Authorization");
try {
Map<String, Object> userData = TokenManager.verifyToken(bearer);
req.setAttribute("userData", userData);
return true;
} catch (Exception ex) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
// 리소스 정리 로직
}
}
인터터 등록 및 예외 경로 설정
WebMvcConfigurer를 구현하여 인터셉터를 등록하고, 특정 경로는 검증에서 제외합니다.
InterceptorConfig
package com.example.config;
import com.example.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/api/auth/login",
"/api/auth/signup",
"/doc.html/**",
"/swagger-resources/**",
"/webjars/**",
"/v3/**"
);
}
}
Knife4j 접근 불가 문제 해결
인터셉터 등록 후 Knife4j 문서 페이지가 접근 불가한 경우, excludePathPatterns에 Swagger 관련 경로를 추가해야 합니다.
| 경로 패턴 | 설명 |
|---|---|
/doc.html/** | Knife4j 메인 UI 페이지 |
/swagger-resources/** | Swagger 리소스 정보 |
/webjars/** | 정적 웹 자원 |
/v3/** | OpenAPI 3.0 스펙 엔드포인트 |
Spring Boot 3.x 환경에서 Knife4j를 사용할 때는 위 4개 패턴을 모두 제외 목록에 포함시켜야 정상적으로 문서 페이지에 접근할 수 있습니다.