diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/LoginDto.java b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/LoginDto.java index 73fda179..bf2579a9 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/LoginDto.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/LoginDto.java @@ -1,3 +1,17 @@ package com.gltkorea.icebang.auth.dto; -public class LoginDto {} +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginDto { + private String email; // 로그인용 이메일 + private String password; // 비밀번호 +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/SignUpDto.java b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/SignUpDto.java index 5dc869fa..0e063750 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/SignUpDto.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/dto/SignUpDto.java @@ -1,3 +1,18 @@ package com.gltkorea.icebang.auth.dto; -public class SignUpDto {} +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SignUpDto { + private String email; // 회원가입용 이메일 + private String password; // 비밀번호 + private String nickname; // 닉네임 +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/provider/DefaultProvider.java b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/provider/DefaultProvider.java index 40a44c7c..da3e3ff8 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/provider/DefaultProvider.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/provider/DefaultProvider.java @@ -1,19 +1,98 @@ package com.gltkorea.icebang.auth.provider; +/* +@RequiredArgsConstructor 추가 +AuthService 의존성 주입 +supports() 메서드 로직 구현 +authenticate() 메서드 로직 구현 +createAuthentication() 헬퍼 메서드 추가 +supports(): SignUpDto나 LoginDto 존재 여부로 처리 가능성 판단 +authenticate(): 회원가입 후 자동 로그인 또는 단순 로그인 처리 +createAuthentication(): Spring Security의 Authentication 객체 생성 +ROLE_USER 기본 권한 부여 + */ + +import java.util.Collections; +import java.util.List; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import com.gltkorea.icebang.auth.dto.DefaultRequestWrapper; +import com.gltkorea.icebang.auth.service.AuthService; +import com.gltkorea.icebang.dto.UserAuthDto; + +import lombok.RequiredArgsConstructor; @Component("default") +@RequiredArgsConstructor public class DefaultProvider implements AuthProvider { + + private final AuthService authService; + + /** + * 요청이 Default Provider로 처리 가능한지 검증 + * + * @param request 요청 래퍼 + * @return 처리 가능 여부 + */ @Override public boolean supports(DefaultRequestWrapper request) { - return false; + // SignUpDto나 LoginDto 중 하나라도 있으면 처리 가능 + if (request.getLoginDto() == null && request.getSignUpDto() == null) { + return false; + } + return true; } + /** + * 인증 처리 (회원가입 + 로그인 또는 로그인만) + * + * @param request 요청 래퍼 + * @return Authentication 객체 + */ @Override public Authentication authenticate(DefaultRequestWrapper request) { - return null; + UserAuthDto user; + + // 1. 회원가입 요청 처리 + if (request.getSignUpDto() != null) { + // 회원가입 후 자동 로그인 + user = authService.signUp(request.getSignUpDto()); + } + // 2. 로그인 요청 처리 + else if (request.getLoginDto() != null) { + user = authService.login(request.getLoginDto()); + } + // 3. 잘못된 요청 + else { + throw new IllegalArgumentException("SignUpDto 또는 LoginDto가 필요합니다"); + } + + // 4. Authentication 객체 생성 + return createAuthentication(user); + } + + /** + * UserAuthDto를 Authentication 객체로 변환 + * + * @param user 사용자 정보 + * @return Authentication 객체 + */ + private Authentication createAuthentication(UserAuthDto user) { + // @TODO + // 이미 auth dto는 데이터 베이스에서 읽어온 상태 + // 그럼 여기서 ROLE_USER가 아닌 실제 role을 넣어야함. + List authorities = + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + + return new UsernamePasswordAuthenticationToken( + user.getEmail(), // principal (사용자 식별자) + null, // credentials (비밀번호는 null로 - 이미 인증 완료) + authorities // authorities (권한) + ); } } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthService.java b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthService.java index 30a2a131..4f4222c3 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthService.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthService.java @@ -2,12 +2,12 @@ import com.gltkorea.icebang.auth.dto.LoginDto; import com.gltkorea.icebang.auth.dto.SignUpDto; -import com.gltkorea.icebang.domain.user.model.Users; +import com.gltkorea.icebang.dto.UserAuthDto; public interface AuthService { - Users signUp(SignUpDto signUpDto); + UserAuthDto signUp(SignUpDto signUpDto); // 변경! - Users login(LoginDto loginDto); + UserAuthDto login(LoginDto loginDto); // 변경! - Users loadUser(String identifier); + UserAuthDto loadUser(String identifier); // 변경! } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthServiceImpl.java b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthServiceImpl.java index aa118d2d..668d79d6 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthServiceImpl.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/auth/service/AuthServiceImpl.java @@ -1,25 +1,118 @@ package com.gltkorea.icebang.auth.service; +/* +비밀번호 암호화: PasswordEncoder 사용 +중복 검증: 회원가입 시 이메일 중복 체크 +UUID 생성: 고유한 userId 자동 생성 +예외 처리: 명확한 에러 메시지 +상태 관리: ACTIVE 상태 체크 + */ + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.gltkorea.icebang.auth.dto.LoginDto; import com.gltkorea.icebang.auth.dto.SignUpDto; -import com.gltkorea.icebang.domain.user.model.Users; +import com.gltkorea.icebang.domain.user.UserStatus; +import com.gltkorea.icebang.dto.UserAuthDto; +import com.gltkorea.icebang.mapper.UserMapper; + +import lombok.RequiredArgsConstructor; @Service +@RequiredArgsConstructor public class AuthServiceImpl implements AuthService { + + private final UserMapper userMapper; + private final PasswordEncoder passwordEncoder; // 비밀번호 암호화용 + + /** + * 회원가입 처리 + * + * @param signUpDto 회원가입 정보 + * @return 생성된 사용자 정보 + */ @Override - public Users signUp(SignUpDto signUpDto) { - return null; + @Transactional + public UserAuthDto signUp(SignUpDto signUpDto) { + // 1. 이메일 중복 체크 + Optional existingUser = userMapper.findByEmail(signUpDto.getEmail()); + if (existingUser.isPresent()) { + throw new IllegalArgumentException("이미 존재하는 이메일입니다: " + signUpDto.getEmail()); + } + + // 2. 새 사용자 객체 생성 + UserAuthDto newUser = + UserAuthDto.builder() + .userId(UUID.randomUUID().toString()) + .email(signUpDto.getEmail()) + .password(passwordEncoder.encode(signUpDto.getPassword())) + .provider("default") + .providerId(null) + .status(UserStatus.ONBOARDING.name()) + .name(signUpDto.getEmail().split("@")[0]) // 이메일 앞부분을 임시 이름으로 @TODO:: fix to name + .build(); + + // 3. DB에 저장 + int result = userMapper.insertUser(newUser); + if (result != 1) { + throw new RuntimeException("회원가입 실패"); + } + + // 4. 생성된 사용자 정보 반환 + return newUser; } + /** + * 로그인 처리 + * + * @param loginDto 로그인 정보 + * @return 인증된 사용자 정보 + */ @Override - public Users login(LoginDto loginDto) { - return null; + public UserAuthDto login(LoginDto loginDto) { + // 1. 이메일로 사용자 조회 + Optional userOpt = userMapper.findByEmail(loginDto.getEmail()); + if (userOpt.isEmpty()) { + throw new IllegalArgumentException("존재하지 않는 이메일입니다: " + loginDto.getEmail()); + } + + UserAuthDto user = userOpt.get(); + + // 2. 비밀번호 검증 + if (!passwordEncoder.matches(loginDto.getPassword(), user.getPassword())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다"); + } + + // 3. 계정 상태 체크 + if (!"ACTIVE".equals(user.getStatus())) { + throw new IllegalStateException("비활성화된 계정입니다"); + } + + return user; } + /** + * 사용자 조회 (이메일 또는 userId로) + * + * @param identifier 이메일 또는 userId + * @return 사용자 정보 + */ @Override - public Users loadUser(String identifier) { - return null; + public UserAuthDto loadUser(String identifier) { + // 1. 이메일 형식인지 확인 (@ 포함 여부) + if (identifier.contains("@")) { + return userMapper + .findByEmail(identifier) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다: " + identifier)); + } else { + return userMapper + .findByUserId(identifier) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다: " + identifier)); + } } } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java index 8a81b429..df52ff2b 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java @@ -27,7 +27,8 @@ public SecureRandom secureRandom() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http.authorizeHttpRequests( + return http.csrf(csrf -> csrf.disable()) // 이 줄을 추가하세요 + .authorizeHttpRequests( auth -> auth.requestMatchers(SecurityEndpoints.PUBLIC.getMatchers()) .permitAll() diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java index 0a24605f..2cfa2b67 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java @@ -2,7 +2,15 @@ public enum SecurityEndpoints { PUBLIC( - "/", "/login", "/register", "/api/public/**", "/health", "/css/**", "/js/**", "/images/**"), + "/", + "/login", + "/register", + "/api/public/**", + "/health", + "/css/**", + "/js/**", + "/images/**", + "/v0/auth/**"), // 이 줄 추가! ADMIN("/admin/**", "/api/admin/**", "/management/**", "/actuator/**"), diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/Users.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/Users.java index ab4d28d1..788062b9 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/Users.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/Users.java @@ -1,7 +1,15 @@ package com.gltkorea.icebang.domain.user.model; +import java.time.LocalDateTime; + import com.gltkorea.icebang.domain.user.UserStatus; public class Users { + private Long id; + private String email; + private String password; // Default 로그인시에만 사용 (OAuth는 null) + private String provider; // "default", "kakao", "naver", "google" + private String providerId; // Default: null, OAuth: 소셜 ID private UserStatus status; + private LocalDateTime createdAt; } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/SecurityAuthenticateAdapter.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/SecurityAuthenticateAdapter.java index f33f546f..29f2a795 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/SecurityAuthenticateAdapter.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/SecurityAuthenticateAdapter.java @@ -5,14 +5,14 @@ import com.gltkorea.icebang.auth.service.AuthService; import com.gltkorea.icebang.domain.user.model.UserAccountPrincipal; -import com.gltkorea.icebang.domain.user.model.Users; +import com.gltkorea.icebang.dto.UserAuthDto; public class SecurityAuthenticateAdapter implements UserAuthService { private AuthService authService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Users user = authService.loadUser(username); + UserAuthDto user = authService.loadUser(username); return UserAccountPrincipal.builder().build(); // @TODO users -> userdetail로 } } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserAuthDto.java b/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserAuthDto.java new file mode 100644 index 00000000..1abe8798 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserAuthDto.java @@ -0,0 +1,19 @@ +package com.gltkorea.icebang.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class UserAuthDto { + private String userId; // 기존 필드 (String ID) + private String name; // 기존 필드 + private String email; // 기존 필드 + private String password; // 추가: Default 로그인용 + private String provider; // 추가: "default", "kakao", "naver" + private String providerId; // 추가: OAuth ID + private String status; // 추가: 사용자 상태 + // ... 필요한 다른 필드들 +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserDto.java b/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserDto.java deleted file mode 100644 index 6763bac9..00000000 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/dto/UserDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.gltkorea.icebang.dto; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class UserDto { - private String userId; - private String name; - private String email; - // ... 필요한 다른 필드들 -} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/mapper/UserMapper.java b/apps/user-service/src/main/java/com/gltkorea/icebang/mapper/UserMapper.java index f09a152a..391014c7 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/mapper/UserMapper.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/mapper/UserMapper.java @@ -3,11 +3,43 @@ import java.util.Optional; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; -import com.gltkorea.icebang.dto.UserDto; +import com.gltkorea.icebang.dto.UserAuthDto; -@Mapper // Spring이 MyBatis Mapper로 인식하도록 설정 +@Mapper public interface UserMapper { - // XML 파일의 id와 메서드 이름을 일치시켜야 합니다. - Optional findByEmail(String email); + /** + * 이메일로 사용자 조회 + * + * @param email 사용자 이메일 + * @return 사용자 정보 (Optional) + */ + Optional findByEmail(String email); + + /** + * 새 사용자 생성 (회원가입) + * + * @param user 생성할 사용자 정보 + * @return 생성된 행 수 (성공시 1) + */ + int insertUser(UserAuthDto user); + + /** + * 이메일과 비밀번호로 사용자 조회 (로그인) + * + * @param email 사용자 이메일 + * @param password 비밀번호 + * @return 사용자 정보 (Optional) + */ + Optional findByEmailAndPassword( + @Param("email") String email, @Param("password") String password); + + /** + * 사용자 ID로 조회 + * + * @param userId 사용자 ID + * @return 사용자 정보 (Optional) + */ + Optional findByUserId(String userId); } diff --git a/apps/user-service/src/main/resources/mybatis/mapper/UserMapper.xml b/apps/user-service/src/main/resources/mybatis/mapper/UserMapper.xml index 68be89f9..54feaba2 100644 --- a/apps/user-service/src/main/resources/mybatis/mapper/UserMapper.xml +++ b/apps/user-service/src/main/resources/mybatis/mapper/UserMapper.xml @@ -3,15 +3,58 @@ - SELECT - user_id AS userId, -- DTO의 필드명(userId)과 컬럼명(user_id)이 다르면 별칭(alias)을 사용 + user_id AS userId, name, - email + email, + password, + provider, + provider_id AS providerId, + status FROM - "USER" -- USER는 예약어이므로 큰따옴표로 감싸기 + "USER" WHERE email = #{email} + + + INSERT INTO "USER" (user_id, name, email, password, provider, provider_id, status) + VALUES (#{userId}, #{name}, #{email}, #{password}, #{provider}, #{providerId}, #{status}) + + + + + + + + \ No newline at end of file diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/DatabaseConnectionTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/DatabaseConnectionTest.java index a3dd2e77..932f56a8 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/DatabaseConnectionTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/DatabaseConnectionTest.java @@ -19,7 +19,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; -import com.gltkorea.icebang.dto.UserDto; +import com.gltkorea.icebang.dto.UserAuthDto; import com.gltkorea.icebang.mapper.UserMapper; @SpringBootTest @@ -55,7 +55,7 @@ void findUserByEmailWithMyBatis() { String testEmail = "hong.gildong@example.com"; // when - Optional foundUser = userMapper.findByEmail(testEmail); + Optional foundUser = userMapper.findByEmail(testEmail); // then // 사용자가 존재하고, 이름이 '홍길동'인지 확인 @@ -68,11 +68,11 @@ void findUserByEmailWithMyBatis() { @DisplayName("샘플 데이터가 올바르게 삽입되었는지 확인") void verifyAllSampleDataInserted() { // 사용자 데이터 확인 - Optional hong = userMapper.findByEmail("hong.gildong@example.com"); + Optional hong = userMapper.findByEmail("hong.gildong@example.com"); assertThat(hong).isPresent(); assertThat(hong.get().getName()).isEqualTo("홍길동"); - Optional kim = userMapper.findByEmail("kim.chulsu@example.com"); + Optional kim = userMapper.findByEmail("kim.chulsu@example.com"); assertThat(kim).isPresent(); assertThat(kim.get().getName()).isEqualTo("김철수"); diff --git a/apps/user-service/src/test/resources/sql/create-schema.sql b/apps/user-service/src/test/resources/sql/create-schema.sql index 115603f8..a618a3b4 100644 --- a/apps/user-service/src/test/resources/sql/create-schema.sql +++ b/apps/user-service/src/test/resources/sql/create-schema.sql @@ -23,6 +23,8 @@ CREATE TABLE "USER" ( "resident_number" VARCHAR(100) NULL, "corporate_number" VARCHAR(100) NULL, "business_number" VARCHAR(100) NULL, + "provider" VARCHAR(100) NULL, + "provider_id" VARCHAR(100) NULL, "type" VARCHAR(50) NULL, "department" VARCHAR(100) NULL, "job_title" VARCHAR(50) NULL,