크래프톤 정글 학습 13주차: Spring Boot로 CRUD 게시판 API 서버 구축기

2025. 6. 18. 05:02·WIL

주요 골자

  • 각자 프레임워크 학습 후 포스트 할 수 있는 게시판 만들기

이번 주차 목표는 프레임워크를 사용한 게시판 구현이다. 일단 BE부터 파서 해보자, 라는게 생각임. BE와 FE를 모두 학습하는 방향으로 잡았고, 각 프레임워크는 BE: Spring Boot, FE: React로 골자를 잡았다.

프레임워크 학습은 인프런의 김영한님 강의(실전! 스프링부트 + JPA 활용 1)를 통해 진행하고, 이번 주차 과제인 게시판 구현은 그와 별개로 직접 부딪혀보며 빠르게 뼈대를 잡아보기로 했다. "배우는 건 배우는 거, 해야 할 건 해야 할 거"라는 느낌으로.

이 포스팅은 Spring Boot로 백엔드 API 서버를 구축하고, 그 과정에서 겪었던 수많은 트러블슈팅과 해결 과정을 모두 기록한 문서다.

1. 프로젝트 환경설정

(1) Spring Initializr 및 의존성

모든 시작은 Spring Initializr에서 비롯된다. 프로젝트 설정은 아래와 같이 진행했다.

  • Project: Gradle - Groovy
  • Dependencies:
    • Spring Web: RESTful API와 내장 톰캣 서버 구축의 기본.
    • Spring Data JPA: 데이터베이스 작업을 위한 핵심 라이브러리.
    • Spring Security: 인증/인가 등 보안 기능을 위해 추가.
    • H2 Database: 개발 및 테스트용으로 가볍게 사용할 수 있는 DB.
    • Lombok: 반복적인 코드를 줄여주는 라이브러리.
    • Validation: 데이터 유효성 검증을 위해 추가.
    • jjwt: JWT(JSON Web Token)를 위해 나중에 추가한 라이브러리.

최종적으로 정리된 build.gradle의 의존성은 아래와 같다.

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    
    // JWT 라이브러리
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

(2) application.yml 최종 설정

수많은 연결 오류와 트러블슈팅을 거쳐 최종적으로 완성된 application.yml 설정이다.

# application.yml
spring:
  # 데이터베이스 설정 (파일 기반, 자동 서버 전환 모드)
  datasource:
    # 파일 모드를 기본으로 하되, 동시 접속 시 자동으로 서버 모드로 전환해주는 Best Practice
    url: jdbc:h2:file:./db/crudBoard;AUTO_SERVER=TRUE
    driver-class-name: org.h2.Driver
    username: sa
    password:

  # JPA 및 Hibernate 설정
  jpa:
    hibernate:
      # 앱 재시작 시 데이터 유지를 위해 update 사용
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true # SQL을 보기 좋게 정렬

  # H2 데이터베이스 콘솔 설정
  h2:
    console:
      enabled: true
      path: /h2-console

# JWT 토큰 설정
jwt:
  secret: your-super-secret-key-for-jwt-token-generation-with-at-least-256-bits-long
  expiration: 86400000 # 24시간

# 로깅 레벨 설정 (SQL 쿼리 및 파라미터 확인용)
logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.orm.jdbc.bind: trace

여기서 가장 중요했던 부분은 spring.datasource.url 설정이었다. tcp 모드와 file 모드의 차이를 몰라 Connection refused 오류와 오랫동안 싸웠는데, ;AUTO_SERVER=TRUE 옵션을 붙여 H2 콘솔 동시 접속 문제를 해결하는 것이 핵심이었다.

2. 엔티티와 리포지토리

데이터베이스 테이블과 매핑될 엔티티 클래스와, DB에 접근할 리포지토리 인터페이스를 정의했다.

  • User.java: 사용자 정보를 담는 엔티티.
  • Post.java: 게시글 정보를 담는 엔티티.
    • 특히 User와의 관계에서, 사용자를 삭제할 때 게시글도 함께 삭제되도록 @OnDelete(action = OnDeleteAction.CASCADE) 어노테이션을 추가하여 참조 무결성 문제를 해결했다.
// User.java
@Entity @Table(name = "users")
@Getter @Setter @NoArgsConstructor
@AllArgsConstructor @Builder
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Long id;
    @Column(unique=true, nullable=false) String username;
    @Column(nullable=false) String password;
    @Column(nullable=false) String role;
}
// Post.java
@Entity @Table(name = "posts")
@Getter @Setter @NoArgsConstructor
@Builder @AllArgsConstructor
public class Post {
    @Id @GeneratedValue Long id;
    String title;
    @Lob String content;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id")
    @OnDelete(action = OnDeleteAction.CASCADE) // 사용자 삭제 시 게시글도 함께 삭제
    private User author;
    
    LocalDateTime createdAt;
    @PrePersist void pre() { createdAt = LocalDateTime.now(); }
}
  • UserRepository.java, PostRepository.java: JpaRepository를 상속받아 기본적인 CRUD 기능을 자동으로 구현했다.
// UserRepository.java
public interface UserRepository extends JpaRepository<User,Long> {
    Optional<User> findByUsername(String username);
    boolean existsByUsername(String username);
}

3. 보안 기능 구현 (Spring Security + JWT)

Stateless한 REST API를 위해 JWT 기반의 인증/인가 시스템을 구축했다.

  • SecurityConfig.java: SecurityFilterChain을 Bean으로 등록하는 최신 방식으로 구현했다. CORS 설정, H2 콘솔 접근 허용, JWT 필터 등록 등의 핵심 보안 설정을 여기서 처리한다.
// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    // ... (생성자)

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화
                .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin())) // H2 콘솔 프레임 허용
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS 설정
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 안함
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(toH2Console()).permitAll() // H2 콘솔 접근 허용
                        .requestMatchers("/api/auth/**").permitAll() // 로그인/회원가입 API 접근 허용
                        .anyRequest().authenticated() // 나머지 모든 경로는 인증 필요
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 직접 만든 JWT 필터 추가

        return http.build();
    }
    
    // ... (CorsConfigurationSource, PasswordEncoder Bean 설정)
}
  • JwtUtil, JwtAuthenticationFilter, UserDetailsServiceImpl: JWT 토큰 생성/검증, 요청 헤더에서 토큰을 읽어 인증을 처리하는 필터, 사용자 정보를 불러오는 서비스 등을 구현하여 보안 설정을 완성했다.

4. 서비스와 컨트롤러 구현

  • AuthService, PostService: 회원가입, 로그인, 글 작성 등의 핵심 비즈니스 로직을 담았다.
  • AuthController, PostController: 클라이언트의 요청을 받는 API 엔드포인트를 구현했다. @AuthenticationPrincipal을 사용해 인증된 사용자 정보를 쉽게 가져올 수 있었다.
// AuthController.java
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
    private final AuthService authService;

    @PostMapping("/signup")
    public ResponseEntity<?> signup(@RequestBody @Valid SignupRequest request) {
        authService.signup(request);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/login")
    public ResponseEntity<JwtResponse> login(@RequestBody @Valid LoginRequest request){
        JwtResponse jwt = authService.login(request);
        return ResponseEntity.ok(jwt);
    }
}
// PostController.java
@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;

    @PostMapping
    public ResponseEntity<Post> create(
            @RequestBody @Validated PostDto dto,
            @AuthenticationPrincipal UserDetails userDetails) {
        Post saved = postService.create(dto, userDetails.getUsername());
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }
}

이제 이 API들을 호출하여 실제 게시판 기능을 사용할 수 있는 백엔드 서버가 완성되었다. 다음 단계는 이 API를 활용할 프론트엔드(React)를 구축하는 것이다.

더 공부해볼 것

  • [ ] 어노테이션 @ 정리
  • [ ] MVC 구조에서 model 데이터가 일반 java와 spring은 어떤 차이가 있는지
  • [ ] H2가 다른 RDBMS(MySQL, PostgreSQL)보다 어떤점이 나은지? -> 기술 채택이유
728x90

'WIL' 카테고리의 다른 글

H2가 MySQL, PostgreSQL와의 차이  (0) 2025.06.19
개발 환경에서 프론트-백 서버 분리 이유  (0) 2025.06.19
[Python] 딕셔너리(Dictionary) 문법  (0) 2025.03.29
'WIL' 카테고리의 다른 글
  • H2가 MySQL, PostgreSQL와의 차이
  • 개발 환경에서 프론트-백 서버 분리 이유
  • [Python] 딕셔너리(Dictionary) 문법
ahpicl64
ahpicl64
in the clouds
  • ahpicl64
    구름
    ahpicl64
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • WIL (4)
      • Jungle (36)
      • AWS (2)
      • SQL (2)
      • CS:APP (17)
      • Algorithm (10)
      • K8s (7)
      • 자료 구조 (10)
      • Spring (4)
      • React (0)
      • 운영체제 (1)
      • 기타등등 (2)
      • 이야기 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    DB
    python
    EC2
    Spring boot
    CSAPP
    알고리즘
    트러블슈팅
    자료구조
    k8s
    부하테스트
    DevOps
    CloudFront
    Spring
    S3
    컴퓨터시스템
    IAM
    queue
    어셈블리
    github actions
    AWS
  • 02-21 06:16
  • hELLO· Designed By정상우.v4.10.3
ahpicl64
크래프톤 정글 학습 13주차: Spring Boot로 CRUD 게시판 API 서버 구축기
상단으로

티스토리툴바