관리 메뉴

♠개발자의 작은 서재♠

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 4 ) 본문

Spring MVC/웹 애플리케이션 개발

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 4 )

♠디지털 모험일지♠ 2024. 10. 21. 19:26

33. RESTful API에서 테스트 자동화

API가 기대대로 동작하는지 확인하기 위해서는 테스트가 필요합니다. Spring에서는 MockMvc를 사용하여 API 호출을 시뮬레이션하고, 테스트를 자동화할 수 있습니다.

예시: MockMvc를 사용한 RESTful API 테스트

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnUserById() throws Exception {
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}

위의 코드는 MockMvc를 사용해 /users/1 엔드포인트에 대한 GET 요청을 테스트하는 예시입니다. 요청이 성공적으로 처리되었는지 확인하고, 응답 데이터의 특정 필드가 기대한 값과 일치하는지 테스트할 수 있습니다.

테스트 자동화를 통해 RESTful API의 안정성을 보장하고, 개발 중 발생할 수 있는 오류를 사전에 방지할 수 있습니다.

34. RESTful API와 OAuth2를 사용한 인증

OAuth2는 RESTful API에서 인증 및 권한 부여를 구현하는 강력한 표준입니다. OAuth2는 클라이언트가 액세스 토큰을 사용하여 리소스에 접근할 수 있도록 합니다.

예시: OAuth2 인증 설정

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@EnableWebSecurity
@EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/public").permitAll()
            .anyRequest().authenticated();
    }
}

위의 코드는 OAuth2를 사용한 인증 및 권한 부여 설정을 보여줍니다. /public 경로는 누구나 접근할 수 있지만, 다른 경로는 액세스 토큰이 필요하며 인증된 사용자만 접근할 수 있습니다.

OAuth2를 통해 애플리케이션은 외부 서비스와 안전하게 통합할 수 있으며, 확장 가능한 인증 메커니즘을 제공합니다.

35. RESTful API에서 CORS 설정

CORS(Cross-Origin Resource Sharing)는 다른 도메인에서 서버로의 요청을 허용하거나 차단하는 방식입니다. Spring에서는 간단하게 CORS 설정을 추가할 수 있습니다.

예시: 전역 CORS 설정

import org.springframework.context.annotation.Bean;
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 WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}

위의 코드는 모든 경로(/**)에 대해 특정 도메인(http://example.com)에서 오는 요청을 허용하는 CORS 설정을 보여줍니다. 또한, GET, POST, PUT, DELETE 메서드에 대해 CORS를 허용하고, 자격 증명도 포함할 수 있도록 설정할 수 있습니다.

CORS 설정을 통해 RESTful API는 여러 도메인에서 안전하게 접근할 수 있으며, 외부 서비스와의 통합을 지원할 수 있습니다.

36. RESTful API에서 비동기 처리

RESTful API에서 비동기 처리는 서버의 성능을 높이고, 특히 시간이 오래 걸리는 작업을 비동기적으로 처리함으로써 클라이언트에게 더 빠른 응답을 제공할 수 있습니다. Spring에서는 @Async 어노테이션을 사용하여 비동기 처리를 쉽게 구현할 수 있습니다.

예시: 비동기 처리 구현

import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;

@RestController
public class AsyncController {

    @Async
    @GetMapping("/process")
    public CompletableFuture<String> process() {
        return CompletableFuture.supplyAsync(() -> {
            // 시간이 오래 걸리는 작업 시뮬레이션
            try {
                Thread.sleep(5000); // 5초 지연
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Process completed";
        });
    }
}

위의 코드는 /process 엔드포인트에서 비동기 처리를 구현한 예시입니다. @Async 어노테이션을 사용하여 메서드를 비동기적으로 처리하며, CompletableFuture를 통해 작업이 완료된 후 응답을 반환합니다. 이를 통해 API는 클라이언트 요청에 대해 빠르게 응답할 수 있으며, 시간이 오래 걸리는 작업은 비동기적으로 처리할 수 있습니다.

37. RESTful API에서 로깅 설정

API 개발 시, 요청과 응답을 로깅하는 것은 중요한 디버깅 및 모니터링 도구가 됩니다. Spring Boot는 기본적으로 SLF4J와 Logback을 통해 강력한 로깅 기능을 제공합니다.

예시: 로깅을 통한 요청 및 응답 모니터링

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoggingController {

    private static final Logger logger = LoggerFactory.getLogger(LoggingController.class);

    @GetMapping("/logs")
    public String logExample() {
        logger.info("GET /logs request received");
        return "Logging example";
    }
}

위의 코드는 /logs 엔드포인트에서 로깅을 구현한 예시입니다. Logger 객체를 사용하여 요청이 처리되는 동안 발생한 이벤트를 기록할 수 있으며, 이를 통해 API 요청의 흐름을 추적하거나 문제 발생 시 원인을 분석할 수 있습니다.

로깅은 개발 및 운영 중에 중요한 역할을 하며, 특히 오류 발생 시 신속한 대응을 위해 필수적인 도구입니다.

38. RESTful API에서 WebSocket 통합

RESTful API는 주로 HTTP를 사용하지만, 실시간 데이터를 주고받기 위해 WebSocket을 함께 사용할 수 있습니다. Spring에서 WebSocket을 사용하면 양방향 통신을 쉽게 구현할 수 있습니다.

예시: WebSocket을 사용한 실시간 통신

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {

    @MessageMapping("/sendMessage")
    @SendTo("/topic/messages")
    public String sendMessage(String message) {
        return "Received: " + message;
    }
}

위의 코드는 WebSocket을 사용해 /sendMessage 엔드포인트에서 실시간 메시지 통신을 구현한 예시입니다. 클라이언트가 메시지를 전송하면, 서버는 해당 메시지를 받아 /topic/messages로 다시 브로드캐스트하여 다른 클라이언트들에게 메시지를 전송합니다.

Spring의 WebSocket 지원을 통해 실시간 채팅, 알림 시스템과 같은 기능을 쉽게 구현할 수 있습니다.

39. RESTful API에서 JWT (JSON Web Token) 인증

JWT는 RESTful API에서 클라이언트 인증을 구현할 때 자주 사용되는 방식입니다. JWT를 통해 클라이언트는 서버로부터 발급받은 토큰을 사용해 인증을 수행하며, 서버는 이 토큰을 검증하여 사용자 권한을 확인합니다.

예시: JWT 토큰 발급 및 인증

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class AuthController {

    private final AuthenticationManager authenticationManager;

    public AuthController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public String authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(),
                loginRequest.getPassword()
            )
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // JWT 생성 및 반환
        String jwt = Jwts.builder()
            .setSubject(loginRequest.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1일 유효기간
            .signWith(SignatureAlgorithm.HS512, "SecretKey")
            .compact();

        return jwt;
    }
}

위의 코드는 /login 엔드포인트에서 클라이언트가 로그인할 때, 서버가 JWT 토큰을 발급하고 클라이언트에게 반환하는 방식입니다. 클라이언트는 이후 요청에서 이 토큰을 사용해 인증을 수행할 수 있으며, 서버는 토큰을 검증하여 클라이언트를 인증합니다.

JWT를 사용한 인증 방식은 세션 관리가 필요 없으며, 토큰 기반으로 클라이언트를 인증할 수 있어 RESTful API에 적합한 인증 방식입니다.

40. RESTful API의 성능 최적화: 캐싱

성능을 최적화하기 위해 자주 호출되는 데이터를 캐시에 저장하여 응답 속도를 향상시킬 수 있습니다. Spring에서 @Cacheable 어노테이션을 사용하여 캐싱을 쉽게 적용할 수 있습니다.

예시: 캐싱을 통한 성능 최적화

import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Cacheable("users")
    @GetMapping("/users/{id}")
    public User getUserById(@PathVariable Long id) {
        // 데이터베이스에서 사용자 정보를 조회하는 로직 (예시)
        return userRepository.findById(id).orElse(null);
    }
}

위의 코드는 @Cacheable 어노테이션을 사용하여 특정 사용자 정보를 캐시에 저장하고, 이후 동일한 요청이 들어오면 캐시에서 데이터를 반환하는 방식입니다. 이를 통해 데이터베이스 호출 횟수를 줄이고, 응답 속도를 높일 수 있습니다.

캐싱을 통해 RESTful API의 성능을 향상시키고, 트래픽이 많은 서비스에서 리소스를 효율적으로 사용할 수 있습니다.

41. RESTful API에서 Rate Limiting 적용

API 서버에 대한 과도한 요청을 방지하기 위해 Rate Limiting(요청 제한)을 적용할 수 있습니다. Spring에서는 이를 구현하기 위해 Bucket4j 같은 라이브러리를 사용할 수 있습니다.

예시: Bucket4j를 사용한 Rate Limiting 구현

import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;

@RestController
public class RateLimitController {

    private final Bucket bucket;

    public RateLimitController() {
        Bandwidth limit = Bandwidth.classic(10, Refill.greedy(10, Duration.ofMinutes(1)));
        this.bucket = Bucket4j.builder().addLimit(limit).build();
    }

    @GetMapping("/limited")
    public String limitedEndpoint() {
        if (bucket.tryConsume(1)) {
            return "Request successful";
        } else {
            return "Too many requests, please try again later";
        }
    }
}

위의 코드는 Bucket4j 라이브러리를 사용하여 API 요청을 제한하는 방식입니다. 설정된 대로 1분에 10개의 요청만 허용되며, 요청이 한도를 초과할 경우 "Too many requests" 메시지를 반환하여 요청을 차단합니다.

Rate Limiting을 통해 서버의 과부하를 방지하고, 공정하게 리소스를 분배할 수 있습니다.

42. RESTful API에서 데이터 유효성 검증

클라이언트가 API로 보낸 데이터의 유효성을 검증하는 것은 매우 중요합니다. Spring에서는 @Valid 어노테이션과 BindingResult 객체를 사용해 요청 데이터를 쉽게 검증할 수 있습니다.

예시: 유효성 검증 적용

import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;

@RestController
public class UserController {

    @PostMapping("/users")
    public String createUser(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            return "Invalid user data";
        }

        // 사용자 생성 로직
        return "User created successfully";
    }
}

위의 코드는 /users 엔드포인트로 들어오는 POST 요청에서 유효성 검증을 수행하는 예시입니다. @Valid 어노테이션을 사용해 User 객체의 필드가 유효한지 확인하며, 유효성 검사를 통과하지 못할 경우 에러 메시지를 반환합니다.

유효성 검증을 통해 잘못된 데이터가 저장되지 않도록 방지하고, API의 안정성을 높일 수 있습니다.

43. RESTful API와 파일 업로드 및 다운로드

RESTful API에서 파일을 업로드하고 다운로드하는 기능은 매우 자주 사용됩니다. Spring에서는 MultipartFile 객체를 사용하여 파일을 처리할 수 있으며, 파일 다운로드를 위해 ResponseEntity를 사용하여 파일을 반환할 수 있습니다.

예시: 파일 업로드 구현

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        String uploadDir = "/uploads/";
        File saveFile = new File(uploadDir + file.getOriginalFilename());
        file.transferTo(saveFile);
        return "File uploaded successfully: " + file.getOriginalFilename();
    }
}

위의 코드는 사용자가 /upload 엔드포인트로 파일을 업로드할 때 해당 파일을 서버의 특정 경로에 저장하는 예시입니다. MultipartFile 객체를 사용하여 파일을 쉽게 처리하고, transferTo() 메서드를 사용하여 파일을 지정된 경로에 저장할 수 있습니다.

예시: 파일 다운로드 구현

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;

@RestController
public class FileDownloadController {

    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        File file = new File("/uploads/" + filename);
        Resource resource = new FileSystemResource(file);
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
    }
}

위의 코드는 사용자가 /download/{filename} 엔드포인트로 파일을 요청할 때, 서버에 저장된 파일을 다운로드할 수 있도록 처리하는 예시입니다. FileSystemResource를 사용해 파일을 읽어들이고, ResponseEntity로 파일을 반환하며, HttpHeaders를 통해 다운로드 응답 헤더를 설정할 수 있습니다.

이를 통해 파일 업로드 및 다운로드 기능을 RESTful API에서 손쉽게 구현할 수 있습니다.

44. RESTful API에서 예외 처리

API에서 발생하는 다양한 예외를 전역적으로 처리하여 클라이언트에게 일관된 오류 메시지를 제공할 수 있습니다. Spring에서는 @ControllerAdvice@ExceptionHandler를 사용해 전역 예외 처리를 구현할 수 있습니다.

예시: 전역 예외 처리

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUserNotFoundException(UserNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGlobalException(Exception ex) {
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

위의 코드는 @ControllerAdvice를 사용해 전역 예외 처리 로직을 구현한 예시입니다. 특정 예외(UserNotFoundException)가 발생했을 때는 404 상태 코드와 함께 오류 메시지를 반환하며, 다른 예외는 500 상태 코드와 일반적인 오류 메시지를 반환합니다.

이렇게 전역 예외 처리를 적용함으로써, 다양한 예외 상황에서 일관된 방식으로 클라이언트에게 오류 메시지를 전달할 수 있습니다.

45. RESTful API에서 인증 및 권한 관리

RESTful API에서 인증(Authentication)과 권한(Authorization)은 보안에 필수적입니다. Spring Security를 사용해 이러한 기능을 쉽게 구현할 수 있으며, JWT를 통해 토큰 기반 인증을 적용할 수 있습니다.

예시: JWT를 사용한 인증 구현

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class AuthController {

    private final AuthenticationManager authenticationManager;

    public AuthController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public String authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(),
                loginRequest.getPassword()
            )
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // JWT 토큰 생성 및 반환
        String jwt = Jwts.builder()
            .setSubject(loginRequest.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1일 유효 기간
            .signWith(SignatureAlgorithm.HS512, "SecretKey")
            .compact();

        return jwt;
    }
}

위의 코드는 /login 엔드포인트에서 사용자가 로그인할 때, JWT 토큰을 발급하여 인증을 처리하는 방식입니다. 클라이언트는 이후 이 토큰을 사용해 서버에 인증된 요청을 보낼 수 있습니다. Spring Security를 활용하여 토큰 기반 인증과 권한 부여를 구현할 수 있으며, 이를 통해 API 보안을 강화할 수 있습니다.

Comments