Spring Security & JWT를 이용한 자체 Login & OAuth2 Login(네이버, 카카오) 구현 (1) - User 관련 클래스 생성

오형상·2024년 9월 21일
0

CoinToZ

목록 보기
1/9
post-thumbnail

CoinToZ 프로젝트 - 로그인 기능 구현

이번 CoinToZ 프로젝트에서 저는 로그인 기능을 담당하게 되었습니다. 사용자 인증 및 권한 관리, 소셜 로그인 통합을 목표로 진행한 내용을 정리하였습니다.


1. User 엔티티 생성

User 엔티티는 사용자 정보를 관리하는 핵심 클래스입니다. 사용자 로그인 정보, 소셜 로그인 정보, 업비트 API 통합 관련 정보 등을 저장하며, JPA를 통해 데이터베이스와 연동됩니다.

User 엔티티 코드

@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;
    }
}

주요 필드 설명

1. 사용자 정보 관련 필드

  • userName: 사용자 이름 또는 닉네임을 저장합니다.
  • password: 암호화된 비밀번호를 저장합니다.
  • email: 로그인 ID로 사용되는 사용자 이메일을 저장합니다.
  • imageUrl: 사용자 프로필 이미지 URL을 저장하며, 기본 이미지가 설정될 수 있습니다.

2. 권한 관련 필드

  • userRole: 기본적으로 USER 권한이 부여됩니다. 필요 시 ADMIN으로 승격 가능합니다.
    • UserRole ENUM: USER, ADMIN과 같은 권한을 정의합니다.

3. 소셜 로그인 관련 필드

  • socialType: 소셜 로그인 유형(NAVER, KAKAO, GOOGLE 등)을 저장합니다.
    • SocialType ENUM: 소셜 로그인 방식을 정의합니다.
  • socialId: 소셜 로그인 시 제공되는 고유 식별자 값을 저장합니다. 자체 로그인 시 null로 설정됩니다.

주요 메소드 설명

1. 권한 관리 메소드

  • promoteRole: 사용자의 권한을 ADMIN으로 승격합니다.
  • demoteRole: 사용자의 권한을 USER로 강등합니다.

2. 사용자 정보 업데이트 메소드

  • updateUser: 사용자 이름과 프로필 이미지를 업데이트합니다.
  • updatePassword: 비밀번호를 암호화된 상태로 업데이트합니다.
  • updateUserName: 사용자 이름을 업데이트합니다.

3. 엔티티 저장 전 처리 메소드

  • prePersist: 저장 전 호출되어, 기본 사용자 권한을 USER로 설정하고, 프로필 이미지가 없을 경우 기본 이미지로 설정합니다.

2. 유저 권한, 소셜 타입 Enum 생성

UserRole Enum

@Getter
@RequiredArgsConstructor
public enum UserRole {
    ADMIN("ROLE_ADMIN"), USER("ROLE_USER");

    private final String key;
}

UserRole Enum은 스프링 시큐리티에서 권한에 접두사 "ROLE_"을 붙여야 하기 때문에, 이를 자동으로 처리할 수 있게 key 필드를 추가하였습니다.

SocialType Enum

public enum SocialType {
    NAVER, KAKAO
}

3. UserRepository 생성

public interface UserRepository extends JpaRepository<User, Integer> {
    Optional<User> findByUserName(String userName);
    Optional<User> findByEmail(String email);

    /**
     * 소셜 타입과 소셜의 식별값으로 회원 찾는 메소드
     */
    Optional<User> findBySocialTypeAndSocialId(SocialType socialType, String socialId);
}

4. Request 생성

@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로 사용됩니다.


5. Response 생성

@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();
    }
}

6. UserService 생성

@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);
    }
}

7. UserController 생성

@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 관련 클래스에 대해 작성해보겠습니다.


0개의 댓글