[Spring Boot 3.x] 1. 회원가입 구현

2025. 2. 2. 05:11·Spring Boot/Sprng Boot Sample Code

 

거의 모든 웹 애플리케이션의 시작인 회원가입 로그인 중 회원가입을 구현해 보려 한다.

 

1. 개발환경

기본적으로 스프링부트 3, 자바는 17을 사용중이다.

데이터 베이스는 MySQL

JPA를 사용해 따로 SQL문을 작성하지 않고 작업하고 있다.

카테고리 도구/기술
프로그래밍 언어 Java 17
프레임워크 Spring Boot 3.4.2
빌드 도구 gradle
데이터베이스 MySQL
ORM 도구 Spring Data JPA
IDE IntelliJ
버전 관리 Git, GitHub, GitLab
패키지 관리 Maven Central
API 문서화 Swagger
로깅 SLF4J

 

2. build.gradle

의존성은 기본적인 프로젝트 실행을 위한 web starter와, mysql, jpa, 비밀번호 암호화를 위한 security 그리고 편의를 위해 lombok 정도를 사용한다.

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

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // Security - BCryptPasswordEncoder 사용
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // MySQL db
    runtimeOnly 'com.mysql:mysql-connector-j'

    // JPA 사용
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // Web 스타터 - 아파치 톰켓, Web MVC, JSON 직렬화&역직렬화, validation
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // getter, setter 등의 어노테이션
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // 테스트 관련
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

3. 디렉토리 구조

디렉토리 구조는 기본적으로 DDD(Domain-Driven Design, 도메인 주도 설계)을 따라가려 했다.

📂 src
 ├── 📂 main
 │   ├── 📂 java
 │   │   └── 📂 com.example.springboot3template
 │   │       ├── 📄 Springboot3templateApplication.java
 │   │       │
 │   │       ├── 📂 auth
 │   │       │   ├── 📂 application          # 🌟 애플리케이션 서비스 계층
 │   │       │   │   ├── 📂 dto
 │   │       │   │   │   ├── 📂 mapper
 │   │       │   │   │   ├── 📂 req
 │   │       │   │   │   │   └── 📄 SignUpReq.java
 │   │       │   │   │   ├── 📂 res
 │   │       │   │   └── 📂 service
 │   │       │   │       └── 📄 AuthService.java
 │   │       │   │
 │   │       │   ├── 📂 domain               # 🌟 도메인 계층
 │   │       │   │   ├── 📂 entity
 │   │       │   │   │   ├── 📄 User.java
 │   │       │   │   │   ├── 📄 UserRoleEnum.java
 │   │       │   │
 │   │       │   ├── 📂 infrastructure       # 🌟 인프라 계층 (데이터 저장소)
 │   │       │   │   ├── 📂 repository
 │   │       │   │   │   └── 📄 UserRepository.java
 │   │       │   │
 │   │       │   ├── 📂 presentation         # 🌟 프레젠테이션 계층
 │   │       │   │   └── 📂 controller
 │   │       │   │       └── 📄 AuthController.java
 │   │       │
 │   │       ├── 📂 common
 │   │       │   └── 📂 security
 │   │       │       └── 📄 SecurityConfig.java
 │   │
 │   ├── 📂 resources
 │   │   ├── 📄 application-local.yml
 │   │   ├── 📄 application.yml

 

4. 구현 순서

entity -> repository -> controller -> dto -> service

(일반적인 개발순서는 다음( entity -> repository -> dto -> service -> controller )과 같으나 난 controller 부터 개발하는게 편해 취향 차이인것 같다. )

 

5. 코드

1. User 엔티티

데이터베이스 테이블과 매핑되는 객체로 각 필드들은 db의 칼럼과 매칭된다.

@Id : 모든 엔티티는 하나의 고유 식별자를 갖어야 한다.

@Entity
@Table(name = "user_info_tb")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;

    @Column
    private String username;

    @Column
    private String password;

    @Column
    @Enumerated(EnumType.STRING)
    private UserRoleEnum role;

}

 

2. 역할 enum

public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

 

3. repository

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByUsername(String username);

}

 

4. controller

@RequestMapping("/api/v1/auth")
@RestController
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;

    @PostMapping("/sign-up")
    public ResponseEntity<?> signUp(@RequestBody SignUpReq req) {
        authService.signUp(req);
        return ResponseEntity.ok().build();
    }

}

 

5. dto (SignUpReq)

@Data
public class SignUpReq {

    private String username;

    private String password;

    private UserRoleEnum role;

    private String adminToken;
    
}

 

6. service

@Service
@RequiredArgsConstructor
public class AuthService {

    private static final String ADMIN_TOKEN = "adminToken";

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    @Transactional
    public void signUp(SignUpReq req) {
        String username = req.getUsername();
        String password = passwordEncoder.encode(req.getPassword());
        UserRoleEnum role  = req.getRole();

        // 회원 중복 확인
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
        }

        if(role == null) {
            role = UserRoleEnum.USER;
        } else if(role == UserRoleEnum.ADMIN) {
            if(req.getAdminToken() == null || !req.getAdminToken().equals(ADMIN_TOKEN)) {
                throw new IllegalArgumentException("유효한 관리자 토큰이 필요합니다.");
            }
        }

        // 사용자 등록
        User user = User.builder()
            .username(username)
            .password(password)
            .role(role)
            .build();

        userRepository.save(user);
    }

}

 

7. securityconfig

@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
public class SecurityConfig {

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf((csrf) -> csrf.disable());

        // Session 비활성화
        http.sessionManagement((sessionManagement) ->
            sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        // 요청 권한 설정
        http.authorizeHttpRequests((authorizeHttpRequests) ->
            authorizeHttpRequests
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
                // api 요청 허가
                .requestMatchers("/api/v1/auth/**").permitAll() // 회원가입 로그인은 필터 통과
                // HTML 페이지 접근을 인증 없이 허용
//                .requestMatchers("/").permitAll()
                .anyRequest().authenticated() // 그 외 모든 요청 인증처리
        );

        return http.build();
    }

}
저작자표시 비영리 변경금지 (새창열림)

'Spring Boot > Sprng Boot Sample Code' 카테고리의 다른 글

Spring Boot 에서 네이버 클라우드 Object Storage 에 파일 업로드  (1) 2025.03.03
[SpringBoot 3.x] 4. @PreAuthorize 어노테이션을 이용한 API 인가 설정  (0) 2025.03.03
[Spring Boot 3.x] 3. JWT 토큰 인증(filter) & API 테스트  (0) 2025.03.01
[Spring Boot 3.x] 2. JWT 토큰과 쿠키를 이용한 로그인 구현  (0) 2025.02.24
build.gradle 이해하기  (1) 2025.01.30
'Spring Boot/Sprng Boot Sample Code' 카테고리의 다른 글
  • [SpringBoot 3.x] 4. @PreAuthorize 어노테이션을 이용한 API 인가 설정
  • [Spring Boot 3.x] 3. JWT 토큰 인증(filter) & API 테스트
  • [Spring Boot 3.x] 2. JWT 토큰과 쿠키를 이용한 로그인 구현
  • build.gradle 이해하기
Yun-seul
Yun-seul
재능이 없으면 열심히라도 하자
  • Yun-seul
    윤슬
    Yun-seul
  • 전체
    오늘
    어제
    • 분류 전체보기 (22)
      • Java (1)
        • Java 기본 (1)
      • Spring Boot (9)
        • Sprng Boot Sample Code (9)
      • Docker (1)
      • Kubernetes (7)
        • Common (3)
        • GKE(Google Kubernetes Engin.. (4)
        • EKS(Elastic Kubernetes Serv.. (0)
      • Redis (0)
      • AWS (0)
      • Git (0)
      • Reflection (1)
      • Troubleshooting (3)
      • Performance Tuning (0)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    rfc6750
    GKE
    전역예외처리
    globalexception
    필터2번
    unable to create tempdir. java.io.tmpdir is set to
    임시디렉토리
    onceperrequestfilter
    djava.io.tmpdir
    커스텀익셉션
    SpringBoot
    jwt토큰 #로그인 #회원가입 #쿠키 #보안설정
    methodargumentnotvalidexception
    에러코드관리
    재요청
    어세스토큰
    쿠버네티스
    docker
    kubernetes
    @value #null #어노테이션 #springboot #springioc #컨테이너
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Yun-seul
[Spring Boot 3.x] 1. 회원가입 구현
상단으로

티스토리툴바