쿠키 탈취와 CSRF 방지를 위한 구현 방법

  1. 쿠키 보안 설정
java
Copy
// Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyOff()))
            .headers(headers -> headers
                .contentSecurityPolicy("script-src 'self'"))
            .cookie(cookie -> cookie
                .secure(true)// HTTPS만 사용
                .httpOnly(true)// JavaScript에서 접근 불가
                .sameSite("Strict"));// 동일 도메인에서만 쿠키 전송

        return http.build();
    }
}

  1. CSRF 토큰 구현
java
Copy
@Controller
public class AuthController {

    @Autowired
    private CsrfTokenRepository csrfTokenRepository;

// CSRF 토큰 생성 및 쿠키 저장
    @GetMapping("/csrf-token")
    public ResponseEntity<Map<String, String>> getCsrfToken(HttpServletRequest request, HttpServletResponse response) {
        CsrfToken token = csrfTokenRepository.generateToken(request);
        csrfTokenRepository.saveToken(token, request, response);

        Map<String, String> body = new HashMap<>();
        body.put("token", token.getToken());

        return ResponseEntity.ok(body);
    }

// CSRF 토큰 검증이 필요한 엔드포인트
    @PostMapping("/api/data")
    public ResponseEntity<?> processData(@RequestHeader("X-CSRF-TOKEN") String token) {
// 토큰 검증 로직
        if (!isValidToken(token)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok("처리 완료");
    }
}

  1. 프론트엔드에서의 CSRF 토큰 처리
javascript
Copy
// API 요청 시 CSRF 토큰 포함
async function fetchWithCsrf(url, options = {}) {
// CSRF 토큰 가져오기
    const csrfToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('XSRF-TOKEN'))
        ?.split('=')[1];

    const defaultHeaders = {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': csrfToken
    };

    const response = await fetch(url, {
        ...options,
        credentials: 'include',// 쿠키 포함
        headers: {
            ...defaultHeaders,
            ...options.headers
        }
    });

    if (!response.ok) {
        throw new Error('API request failed');
    }

    return response.json();
}

// 사용 예시
try {
    const data = await fetchWithCsrf('/api/data', {
        method: 'POST',
        body: JSON.stringify({ key: 'value' })
    });
} catch (error) {
    console.error('Request failed:', error);
}

  1. 세션 관리
java
Copy
@Configuration
public class SessionConfig {

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

// 세션 설정
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
            .requestMatchers("/resources/**");
    }

// 세션 리스너
    @Component
    public class SessionListener implements HttpSessionListener {
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            HttpSession session = se.getSession();
            session.setMaxInactiveInterval(1800);// 30분
        }
    }
}

  1. XSS 방지
java
Copy
// HTML 이스케이프 유틸리티
public class SecurityUtil {
    public static String escapeHtml(String html) {
        if (html == null) {
            return null;
        }
        return html.replace("&", "&amp;")
                  .replace("<", "&lt;")
                  .replace(">", "&gt;")
                  .replace("\\"", "&quot;")
                  .replace("'", "&#x27;");
    }
}

// 컨트롤러에서 사용
@PostMapping("/comment")
public ResponseEntity<?> addComment(@RequestBody String comment) {
    String safeComment = SecurityUtil.escapeHtml(comment);
// 저장 로직
    return ResponseEntity.ok().build();
}

  1. JWT 토큰 사용
java
Copy
@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String secretKey;

// 토큰 생성
    public String createToken(String username) {
        Claims claims = Jwts.claims().setSubject(username);
        Date now = new Date();

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + 1800000))// 30분
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

// 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

  1. 보안 헤더 설정
java
Copy
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SecurityInterceptor());
    }

    public class SecurityInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) {

            response.setHeader("X-Content-Type-Options", "nosniff");
            response.setHeader("X-Frame-Options", "DENY");
            response.setHeader("X-XSS-Protection", "1; mode=block");
            response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");

            return true;
        }
    }
}

주요 보안 포인트:

  1. HttpOnly 쿠키 사용
  2. Secure 플래그 설정
  3. SameSite 속성 사용
  4. CSRF 토큰 검증