(MSA)3.auth 모듈개발

Published: by Creative Commons Licence

Spring Boot JWT 인증 서버 구성하기 (Hexagonal Architecture + MySQL)

이번 글에서는 Spring Boot 기반으로 JWT 인증 서버를 구축하는 방법을 정리합니다.
아키텍처는 헥사고날 아키텍처(Hexagonal Architecture) 패턴을 따르며, DB는 MySQL, 빌드 도구는 Gradle을 사용합니다. 이 구조는 확장성과 테스트 편의성이 뛰어나며, 도메인 → 유스케이스 → 어댑터로 흐름이 깔끔하게 나뉘어 유지보수가 매우 좋습니다.


📦 패키지 구조

com.enjoywalk.auth
│
├── adapter                        # 외부 시스템과의 연결 (입력/출력 어댑터)
│   ├── in                         # 외부에서 들어오는 요청 처리 (ex. Controller)
│   │   └── web                    # REST API 컨트롤러 위치
│   │       └── mapper             # Request/Response <-> DTO 변환용 매퍼
│   └── out                        # 외부로 나가는 처리 (DB, 외부 API 등)
│       └── persistence            # 데이터베이스 접근 어댑터
│           ├── entity             # JPA 엔티티 클래스
│           └── mapper             # Entity <-> Domain 모델 매핑
│
├── application                    # 유스케이스 계층 (비즈니스 로직 흐름 처리)
│   ├── port                       # 의존성 역전 지점 (인터페이스 기반)
│   │   ├── in                     # 유스케이스 입력 포트 (ex. 서비스 인터페이스)
│   │   └── out                    # 유스케이스 출력 포트 (ex. DB 호출 인터페이스)
│   └── service                    # 유스케이스 구현체 (비즈니스 흐름 담당)
│
├── domain                         # 도메인 계층 (핵심 비즈니스 모델과 로직)
│   ├── model                      # 도메인 모델 (ex. Member, Token 등)
│   └── service                    # 도메인 정책/로직 처리 (순수 비즈니스 로직)
│
├── infrastructure                # 기술적 세부 구현 (보안, 설정, JWT 등)
│   ├── config                     # 보안 설정, CORS, Bean 설정 등
│   ├── filter                     # 커스텀 필터 (ex. JWT 인증 필터)
│   └── jwt                        # JWT 토큰 생성 및 검증 로직
│
└── AuthApplication.java           # 스프링 부트 메인 실행 클래스

설명

  • mapper 패키지가 adapter의 in/out 구간에서 각각의 패키지로 구분되도록 구성
  • infrastructure 패키지 추가해서 기술적 세부 구현을 분리

⚙️ Gradle 설정 (build.gradle)

plugins {
    id 'java'
    id 'org.springframework.boot'
    id 'io.spring.dependency-management'
}

dependencies {
    implementation project(':common') // common 모듈 의존성 추가
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 처리용
}

🛠️ application.yml 설정

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
  h2:
    console:
      enabled: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  sql:
    init:
      mode: always
      schema-locations: classpath:h2\schema.sql

decorator:
  datasource:
    p6spy:
      enable-logging: false  # H2일 땐 P6Spy 굳이 끌 수도 있어요

jwt:
  secret: "4c6Vb@tLq9kZ$J6sB3rU^7u8yA0M2tX2b3LzJ1tQG9y1kB5aV&yP7sVn3zB8xW"
  expiration: 86400000

✅ logback-spring.xml 설정 예시

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <!-- 콘솔 출력 -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- P6Spy SQL 로그 전용 로거 -->
  <logger name="p6spy" level="DEBUG" additivity="false">
    <appender-ref ref="CONSOLE"/>
  </logger>

  <!-- 전체 루트 로그 -->
  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
  </root>

</configuration>

🔐 Spring Security 설정 (SecurityConfig)

package com.enjoywalk.auth.infrastructure.config;

import com.enjoywalk.auth.infrastructure.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable) // CSRF 비활성화
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 모든 요청에 대해 JWT 인증 필터 추가
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

🔁 구성 흐름 요약

[Request] 
   → Adapter In (Web: Controller)
      → Application Port In (UseCase Interface)
         → Application Service (유스케이스 구현체)
            → Domain Model / Domain Service
            → Application Port Out (Repository Interface)
               → Adapter Out (JPA, 외부 API 등)