SpringBootProject

Project Part1 - 로그인 구현하기. (1. 기본적인 로직)

dding-shark 2024. 11. 24. 11:00
728x90

-1. 로그인을 할 수 있는 여러가지 방식과 그에 따른 장단점을 분석해보는 작업을 하고 있다.

-2. 공통 로그인 서비스 구현.

  • User에 관련한 Entity, Dto, Repository, Service, Controller, View 등등을 구현한다.
  • 회원가입 서비스의 기본적인 로직.
  • User (클라이언트) 의 입력
    • Id
    • Password
    • PasswordCheck
    • Nickname
  • Frontend
    • Id, Nickname 중복 체크및 Data 적합성 판단
    • Backend 에 Data 전달.(Id,Password,Nickname)
  • Backend
    • DB Connection
    • Id, Nickname 중복 체크 <- 중봅 작업 이라고 생각할 수 있지만, 양쪽 단에서 체크 하는 것이 더 좋다고 들었습니다...
    • 유저 정보 저장후 성공 여부 전송
    • DB Disconnection
  • Frontend
    • 전송된 성공 여부에 관한 페이지 출력

2-1 . 설계및 코드 구현.

-1. User Entity 설계.

User Table :

  • id(id, notnull,long)
  • loginId(notnull, String,unique) // 로그인을 위한 id
  • Password(notnull, String)
  • Nickname(notnull,String,unique)
  • USER_ROLE(notnull, String, "USER or ADMIN")

추가 명세서 :

  • id: index 를위한 sql ID.
  • loginId : 영문, 숫자로 이루어진 5~12자 이내 String. // 이라 했지만 일단 중복만..
  • Password : 영문, 숫자, 특수문자로 이루어진 5~18자 이내 String. // 이라 했지만 일단 중복만..
  • Nickname : 한글, 영문 ,숫자, 특수문자로 이루어진 2~10자 이내 String. // 이라 했지만 일단 중복만..

__-2. [User.java]

@Entity
@Builder
@Getter 
@NoArgsConstructor
@AllArgsConstructor


public class User {

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

    private String loginId;

    private String password;

    private String nickname;

    private UserRole role;

}

__-3. [UserRole.java]

public enum UserRole {
    USER, ADMIN;
}

-4. DTO

@Getter
@Setter
@NoArgsConstructor
public class JoinRequest {

    @NotBlank(message = "로그인 아이디가 비어있습니다.") // validation을 이용한 Global 에러처리.
    private String loginId;

    @NotBlank(message = "비밀번호가 비어있습니다.")
    private String password;
    private String passwordCheck;

    @NotBlank(message = "닉네임이 비어있습니다.")
    private String nickname;



    //  TODO : 비밀번호 암호화 구현 -> 시큐리티 적용 다음
    public User toEntity() {
        return User.builder()
                .loginId(this.loginId)
                .password(this.password)
                .nickname(this.nickname)
                .role(UserRole.USER)
                .build();
    }
}
- [LoginRequest.java]

```java
@Getter
@Setter
@NoArgsConstructor
public class LoginRequest {

    private String loginId;
    private String password;

}

어디서나 볼 수 있는 그런 LoginRequestDto 이다.

  • [UserService.java]
    • 코드를 작성하기전에 서비스 클래스에서 구현해야할 서비스 로직들을 생각해봤다.
      1. loginId 중복 체크
      2. nickname 중복 체크
      3. 패스워드 인코딩 -> 여기서 설명하는거보다, 시큐리티를 접목시켰을때 설명하는게 좋아보여서 여기선 구현하진 않았다.
      4. User 정보 서버에 저장하기. -> 회원가입 완료
      5. 회원가입 가능여부 전송하기.(에러 처리하기)
      6. 로그인 기능 <- 로그인 보안 기법을 사용하지 않은 Row한 로그인 상태임으로, User정보 전송하기
    • Issue
      • 어떻게 구현을 구현을 하던중에.. 회원가입 가능여부 전송하기. 이 에러처리를 어디서 해야할지 고민을 한번 심도 있게 해봤다.
        1. Controller 에서 처리하기. <- 가장 중복체크의 로직속성을 생각하면, 일반적인 방법인거 같다.
        2. Service에서 처리하기 <- 유지보수 및 코드 재활용을 고려한다면, 이방법이 좋은거 같지만 Controller 에서 하는거보단 처리속도가 느리다는 단점이 있다.
          • 이러한 방법이 있을수 있다고 생각을 했는데, 처리 속도를 보면 Controller 단, 유지보수 + 나중에 토큰 세션 등등을 생각하면 Service에서 하는게 맞다고 생각 했다.
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;


  // Controller 에서 처리할까 Service에서 처리할까 고민하다가. 결국 Service를 선택 
    /**
     * loginId 중복 체크
     * 회원가입 기능 구현 시 사용
     * 중복되면 true 
     */
    public boolean checkLoginIdDuplicate(String loginId) {
        return userRepository.existsByLoginId(loginId);
    }

    /**
     * nickname 중복 체크
     * 회원가입 기능 구현 시 사용
     * 중복되면 true 
     */
    public boolean checkNicknameDuplicate(String nickname) {
        return userRepository.existsByNickname(nickname);
    }

    /**
     * 회원가입 기능
     * View 에서 JoinRequest(loginId, password, nickname)을 입력받아 User로 변환 후 저장
     * loginId, nickname 중복 체크는 Controller에서 진행후 메세지 쿨력.
     * Controller 에서 Service 를 불러와 처리하기에 결국 처리 하는곳은 Service이다. 오해 하기 금지.
     */
    public void join(JoinRequest req) {
        userRepository.save(req.toEntity());
    }


    /**
     *  로그인 기능
     *  화면에서 LoginRequest(loginId, password)을 입력받아 loginId와 password가 일치하면 User return
     *  loginId가 존재하지 않거나 password가 일치하지 않으면 null 
     */
    public User login(LoginRequest req) {
        Optional<User> optionalUser = userRepository.findByLoginId(req.getLoginId());

        // loginId와 일치하는 User가 없으면 null return
        if(optionalUser.isEmpty()) {
            return null;
        }

        User user = optionalUser.get();

        // 찾아온 User의 password와 입력된 password가 다르면 null return
        if(!user.getPassword().equals(req.getPassword())) {
            return null;
        }

        return user;
    }

    /**
     * userId(Long)를 입력받아 User을 return 해주는 기능
     * 인증, 인가 시 사용
     * userId가 null이거나(로그인 X) userId로 찾아온 User가 없으면 null 
     * userId로 찾아온 User가 존재하면 User return
     */
    public User getLoginUserById(Long userId) {
        if(userId == null) return null;

        Optional<User> optionalUser = userRepository.findById(userId);
        if(optionalUser.isEmpty()) return null;

        return optionalUser.get();
    }

    /**
     * loginId(String)를 입력받아 User을 return 해주는 기능
     * 인증, 인가 시 사용
     * loginId가 null이거나(로그인 X) userId로 찾아온 User가 없으면 null 
     * loginId로 찾아온 User가 존재하면 User return
     */
    public User getLoginUserByLoginId(String loginId) {
        if(loginId == null) return null;

        Optional<User> optionalUser = userRepository.findByLoginId(loginId);
        if(optionalUser.isEmpty()) return null;

        return optionalUser.get();
    }
}

서비스 이다. PasswordEncoder는 나중에 시큐리티를 이용할때 다시 설명하고 구현하는 것으로 하고,

문서화를 연습하기 위한 서비스 임으로 각 서비스 마다 로직에 대해 주석을 달아 두었다.

-4. __[UserRepository.java]

public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByLoginId(String loginId);
    boolean existsByNickname(String nickname);
    Optional<User> findByLoginId(String loginId);
}

Service를 구현하고 필요한 리포지토리들을 선언 하였다.
SpringBoot에서의 리포지토리는 나중에 장문의 글을 한번 쓸 예정이다.


728x90