이번 CoinToZ 프로젝트에서 저는 로그인 기능을 담당하게 되었습니다. 사용자 인증 및 권한 관리, 소셜 로그인 통합을 목표로 진행한 내용을 정리하였습니다.
User
엔티티는 사용자 정보를 관리하는 핵심 클래스입니다. 사용자 로그인 정보, 소셜 로그인 정보, 업비트 API 통합 관련 정보 등을 저장하며, JPA를 통해 데이터베이스와 연동됩니다.
@Entity
@Setter
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Where(clause = "deleted_at IS NULL") // 논리적 삭제 (deleted_at 컬럼이 NULL인 데이터만 조회)
@SQLDelete(sql = "UPDATE user SET deleted_at = CURRENT_TIMESTAMP WHERE user_id = ?") // 논리적 삭제 처리
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Primary Key 자동 생성
@Column(name = "user_id")
private Integer id; // 사용자 고유 ID
private String userName; // 사용자 이름
private String password; // 암호화된 사용자 비밀번호
private String email; // 사용자 이메일 (로그인 ID)
@Column(columnDefinition = "longtext")
private String imageUrl; // 사용자 프로필 이미지 URL
@Enumerated(EnumType.STRING)
@Column(length = 10, columnDefinition = "varchar(10) default 'USER'")
private UserRole userRole; // 사용자 권한 (예: ADMIN, USER)
@Enumerated(EnumType.STRING)
private SocialType socialType; // 소셜 로그인 타입 (KAKAO, NAVER)
private String socialId; // 소셜 로그인 고유 식별자 (일반 로그인인 경우 null)
@PrePersist
public void prePersist() {
this.userRole = this.userRole == null ? USER : this.userRole; // 기본 권한 설정
this.imageUrl = this.imageUrl == null ? "https://ohy1023.s3.ap-northeast-2.amazonaws.com/basic.png" : this.imageUrl; // 기본 프로필 이미지 설정
}
public User promoteRole(User user) {
user.userRole = ADMIN;
return user;
}
public User demoteRole(User user) {
user.userRole = USER;
return user;
}
public void updateUser(String userName, String imageUrl) {
this.userName = userName;
this.imageUrl = imageUrl;
}
public void updatePassword(String newPassword) {
this.password = newPassword;
}
public void updateUserName(String userName) {
this.userName = userName;
}
}
userName
: 사용자 이름 또는 닉네임을 저장합니다.password
: 암호화된 비밀번호를 저장합니다.email
: 로그인 ID로 사용되는 사용자 이메일을 저장합니다.imageUrl
: 사용자 프로필 이미지 URL을 저장하며, 기본 이미지가 설정될 수 있습니다.userRole
: 기본적으로 USER
권한이 부여됩니다. 필요 시 ADMIN
으로 승격 가능합니다.UserRole ENUM
: USER
, ADMIN
과 같은 권한을 정의합니다.socialType
: 소셜 로그인 유형(NAVER, KAKAO, GOOGLE 등)을 저장합니다.SocialType ENUM
: 소셜 로그인 방식을 정의합니다.socialId
: 소셜 로그인 시 제공되는 고유 식별자 값을 저장합니다. 자체 로그인 시 null
로 설정됩니다.promoteRole
: 사용자의 권한을 ADMIN
으로 승격합니다.demoteRole
: 사용자의 권한을 USER
로 강등합니다.updateUser
: 사용자 이름과 프로필 이미지를 업데이트합니다.updatePassword
: 비밀번호를 암호화된 상태로 업데이트합니다.updateUserName
: 사용자 이름을 업데이트합니다.prePersist
: 저장 전 호출되어, 기본 사용자 권한을 USER
로 설정하고, 프로필 이미지가 없을 경우 기본 이미지로 설정합니다.@Getter
@RequiredArgsConstructor
public enum UserRole {
ADMIN("ROLE_ADMIN"), USER("ROLE_USER");
private final String key;
}
UserRole
Enum은 스프링 시큐리티에서 권한에 접두사 "ROLE_"을 붙여야 하기 때문에, 이를 자동으로 처리할 수 있게 key
필드를 추가하였습니다.
public enum SocialType {
NAVER, KAKAO
}
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByUserName(String userName);
Optional<User> findByEmail(String email);
/**
* 소셜 타입과 소셜의 식별값으로 회원 찾는 메소드
*/
Optional<User> findBySocialTypeAndSocialId(SocialType socialType, String socialId);
}
@Getter
@NoArgsConstructor
public class UserJoinRequest {
private String userName;
private String email;
private String password;
public User toEntity(String password) {
return User.builder()
.userName(this.userName)
.email(this.email)
.password(password)
.build();
}
@Builder
public UserJoinRequest(String userName, String email, String password) {
this.userName = userName;
this.email = email;
this.password = password;
}
}
UserJoinRequest
는 자체 로그인 회원가입 API에서 RequestBody로 사용됩니다.
@Getter
@NoArgsConstructor
public class UserJoinResponse {
private Integer userId;
private String email;
@Builder
public UserJoinResponse(Integer userId, String email) {
this.userId = userId;
this.email = email;
}
public static UserJoinResponse toResponse(User user) {
return UserJoinResponse.builder()
.userId(user.getId())
.email(user.getEmail())
.build();
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder encoder;
@Transactional
public UserJoinResponse join(UserJoinRequest userJoinRequest) {
userRepository.findByEmail(userJoinRequest.getEmail())
.ifPresent(user -> {
throw new AppException(DUPLICATED_EMAIL, DUPLICATED_EMAIL.getMessage());
});
User savedUser = userRepository.save(
userJoinRequest.toEntity(encoder.encode(userJoinRequest.getPassword()))
);
return UserJoinResponse.toResponse(savedUser);
}
}
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("api/v1/users")
public class UserRestController {
private final UserService userService;
@ApiOperation(value = "회원가입")
@PostMapping("/join")
public ResponseEntity<Response<UserJoinResponse>> joinUser(@RequestBody UserJoinRequest userJoinRequest) {
UserJoinResponse userJoinResponse = userService.join(userJoinRequest);
return ResponseEntity.ok().body(Response.success(userJoinResponse));
}
}
위와 같이 User 관련 설정을 마쳤습니다.
다음에는 JWT 관련 클래스에 대해 작성해보겠습니다.