이전 단계에서 데이터베이스 스키마와 기본 도메인 모델을 준비했다면, 이제 인증 및 인가 처리를 위한 핵심 유틸리티 클래스와 시스템 전반에 사용될 설정을 구성해야 합니다. 본 장에서는 API 표준 응답 객체, JWT 처리 로직, CORS 설정, 그리고 API 문서화 도구인 Swagger 구성에 대해 다룹니다.
공통 상수 및 API 응답 객체 정의
코드의 유지보수성을 높이기 위해 반복적으로 사용되는 문자열 값과 API 응답 포맷을 별도의 클래스로 관리합니다.
package com.example.demo.common.constants;
public class SecurityConstants {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String REDIS_USER_PREFIX = "LOGIN_USER:";
public static final int TOKEN_START_INDEX = 7;
}
클라이언트에게 일관된 데이터 형식을 반환하기 위한 제네릭 응답 클래스를 작성합니다. 성공과 실패 경우를 명확히 구분하여 처리합니다.
package com.example.demo.common.dto;
import lombok.Data;
@Data
public class ApiResponse<T> {
private int status;
private String message;
private T data;
public ApiResponse() {}
public ApiResponse(T data) {
this.data = data;
}
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>(data);
response.setStatus(200);
response.setMessage("Success");
return response;
}
public static ApiResponse<Void> success() {
return success(null);
}
public static ApiResponse<Void> fail(int code, String msg) {
ApiResponse<Void> response = new ApiResponse<>();
response.setStatus(code);
response.setMessage(msg);
return response;
}
}
CORS(Cross-Origin Resource Sharing) 설정
프론트엔드와 백엔드가 분리된 환경에서 브라우저 보안 정책으로 인한 리소스 공유 문제를 해결하기 위해 CORS를 허용하는 설정을 추가합니다.
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
Swagger API 문서화 설정
REST API 테스트 및 명세서 작성을 위해 Swagger를 활성화하고 Docket 빈을 등록합니다.
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Security JWT API")
.description("인증 및 인가 관련 API 테스트 문서")
.version("1.0")
.contact(new Contact("Dev Team", "", ""))
.build();
}
}
JWT 생성 및 검증 유틸리티
JWT 토큰의 생성, 파싱, 검증 로직을 담당하는 핵심 유틸리티 클래스입니다. Auth0 라이브러리를 사용하며, 사용자 정보는 Redis에 캐싱하여 관리합니다.
package com.example.demo.security;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.common.constants.SecurityConstants;
import com.example.demo.domain.SysUser;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
@Component
@RequiredArgsConstructor
public class TokenManager {
private static final String SECRET_KEY = "secure_secret_key_2024";
private final RedisTemplate<String, Object> redisTemplate;
public String generateToken(SysUser user) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 30);
Date expireDate = calendar.getTime();
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
return JWT.create()
.withSubject(user.getId().toString())
.withExpiresAt(expireDate)
.withIssuedAt(new Date())
.sign(algorithm);
}
public DecodedJWT parseAndVerifyToken(String headerToken) {
String rawToken = extractBearerToken(headerToken);
if (!StringUtils.hasText(rawToken)) {
return null;
}
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
return JWT.require(algorithm).build().verify(rawToken);
} catch (Exception e) {
return null;
}
}
public SysUser retrieveUserFromCache(DecodedJWT jwt) {
String userIdStr = jwt.getSubject();
if (!StringUtils.hasText(userIdStr)) {
throw new RuntimeException("유효하지 않은 토큰입니다.");
}
String cacheKey = SecurityConstants.REDIS_USER_PREFIX + userIdStr;
SysUser cachedUser = (SysUser) redisTemplate.opsForValue().get(cacheKey);
if (Objects.isNull(cachedUser)) {
throw new RuntimeException("로그인 세션이 만료되었습니다.");
}
return cachedUser;
}
private String extractBearerToken(String authHeader) {
if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
return authHeader.substring(SecurityConstants.TOKEN_START_INDEX);
}
return null;
}
}
이제 모든 필수 유틸리티와 기본 설정이 완료되었습니다. Swagger 접속 주소(http://localhost:8080/swagger-ui.html)를 통해 설정이 정상적으로 로드되었는지 확인할 수 있습니다. 다음 단계에서는 Spring Security의 필터 체인을 구성하여 이 유틸리티들을 실제 인증 로직에 연결할 것입니다.