본문 바로가기
Project/소경관

[소경관] : thymeleaf와 JPA, builder 패턴을 사용하여 로그인 구현하기

by 오주현 2022. 4. 15.
반응형
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "USER_INFO")
@ToString
public class UserEntity {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long userNo;

    @Column(name = "USER_NAME", length = 20)
    private String userName;

    @Column(name = "USER_PN", length = 40)
    private String userPn;

    @NotNull
    @Column(name = "USER_EMAIL", length = 200)
    private String userEmail;

    @NotNull
    @Column(name = "USER_ID", length = 200)
    private String userId;

    @Column(name = "USER_PW", length = 200)
    private String userPw;

    @Column(name = "USER_ADDR", length = 200)
    private String userAddr;

    @Builder
    public UserEntity(Long userNo,
                      String userName,
                      String userPn,
                      String userEmail,
                      String userId,
                      String userPw,
                      String userAddr) {
        this.userNo = userNo;
        this.userName = userName;
        this.userPn = userPn;
        this.userEmail = userEmail;
        this.userId = userId;
        this.userPw = userPw;
        this.userAddr = userAddr;
    }

}

UserEntity를 간단하게 작성하고 @builder 를 사용했다. Entity에서는 데이터 일관성을 유지하기 위해 Setter를 사용하지 않는다고 한다. 그 대안으로 builder pattern을 사용했다.

 

@Data
public class UserVo {

    private Long userNo;

    @NotBlank(message = "이름을 입력해주세요.")
    @NotNull
    private String userName;

    @NotBlank(message = "연락처를 입력해주세요.")
    private String userPn;

    @NotBlank(message = "이메일 주소를 입력해주세요.")
    @Email(message = "올바른 이메일 주소를 입력해주세요.")
    private String userEmail;

    @NotBlank(message = "아이디를 입력해주세요.")
    private String userId;

    @NotBlank(message = "비밀번호를 입력해주세요.")
    @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.")
    private String userPw;

    @Column(name = "USER_ADDR", length = 200)
    private String userAddr;

}

UserVo에서는 회원가입에서 아직 완성하지 못한 검증 로직을 위해 각종 검증 어노테이션을 잠시 사용하고 있다. 추후에 다듬으면서 정리할 예정이다.

 

@Data
public class UserDTO {

    private Long userNo;
    private String userName;
    private String userPn;
    private String userEmail;
    private String userId;
    private String userPw;
    private String userAddr;

    public UserDTO(Long userNo,
                   String userName,
                   String userPn,
                   String userEmail,
                   String userId,
                   String userPw,
                   String userAddr) {
        this.userNo = userNo;
        this.userName = userName;
        this.userPn = userPn;
        this.userEmail = userEmail;
        this.userId = userId;
        this.userPw = userPw;
        this.userAddr = userAddr;
    }

    public UserDTO(Long userNo,
                   String userId,
                   String userPw) {
        this.userNo = userNo;
        this.userId = userId;
        this.userPw = userPw;
    }
}

UserDTO에서는 회원가입에 사용하는 생성자와 로그인에서 사용할 생성자를 만들어줬다. 생성자를 만들어준 이유는 아래 Controller에서 다룬다.

 

/**
 *로그인 페이지
*/
@GetMapping("/user/logIn")
public String userLogin(Model model) {

log.info(this.getClass().getName() + ".user/login 로그인으로 이동 !!");

    model.addAttribute("userVo", new UserVo());

    return "user/logIn";
}

로그인 페이지를 띄워주는 GetMapping을 만들어주면서 Model 객체를 생성해 View로 넘겨준다.

 

이 Model 객체는 vo로 View에서 입력한 값을 받아 사용할 예정이다.

 

<form id="contactForm" th:action="@{/user/logIn/page}" th:object="${userVo}" data-sb-form-api-token="API_TOKEN" method="post">

    <div th:hidden="*{userNo}"></div>

    <div class="form-floating">
        <input class="form-control" id="id" type="text" th:field="*{userId}" placeholder="아이디를 입력해 주세요." data-sb-validations="required" />
        <label for="id">아이디</label>
        <div class="invalid-feedback" data-sb-feedback="name:required">아이디를 입력해 주세요.</div>
    </div>

    <div class="form-floating">
        <input class="form-control" id="pw" type="password" th:field="*{userPw}" placeholder="비밀번호를 입력해 주세요." data-sb-validations="required,email" />
        <label for="pw">비밀번호</label>
        <div class="invalid-feedback" data-sb-feedback="email:required">비밀번호를 입력해 주세요.</div>
    </div>

th:object="${userVo}" 를 통해 위에서 Model 객체에 담아 보내온 Vo에 입력 값을 담아 th:action="@{/user/logIn/page} 로 submit 해준다.

 

<div th:hidden="*{userNo}"></div> userNo는 pk로 설정해 둬서 혹시 사용할 수도 있을 것 같아서 hidden으로 일단 가져와봤다.

 

/**
     * 로그인 로직 처리
     */
    @PostMapping("/user/logIn/page")
    public String Login(@ModelAttribute UserVo userVo, HttpSession session) throws Exception {

        log.info(this.getClass().getName() + "로그인 로직 처리 시작");

        int res = 0;

            log.info("UserDTO에 Vo값 담기 시작");
            UserDTO userDTO = new UserDTO(userVo.getUserNo(), userVo.getUserId(), userVo.getUserPw());

            log.info("userDTO = {}", userDTO);

            res = userService.login(userDTO);

            log.info("res = {}", res);

            if (res == 1) {
                log.info("res = {}", res);
                log.info("로그인 성공");
            } else {
                log.info("res = {}", res);
                log.info("로그인 실패");
                return "user/login";
            }

            log.info(this.getClass().getName() + "로그인 로직 처리 끝");
            userVo = null;

        return "index";
    }

UserController에서 vo 객체에 담긴 데이터를 DTO에 넣어주었다. 아까 UserDTO에서 만든 파라미터를 3개 받는 생성자를 사용한다.

 

DTO에서 생성자를 만들어 사용한 이유는 Controller에서 Setter를 사용하지 않기 위해서이다. 가능하면 Controller는 View와 소통을 위한 공간으로 최소한의 로직만 넣고 싶었다.

 

로직 체크를 int로 받아서 하면 조금 쉽게 할 수 있어서 res를 선언하고 0과 1을 사용해서 체크 로직을 다뤘다.

vo에서 데이터를 받은 dto를 userService로 보내주었다.

 

/**
 * 로그인
 */
int login(UserDTO userDTO) throws Exception;

interface Service를 만들어 주었고 Service에서는 interface를 구현했다.

 

@Override
public int login(UserDTO userDTO) throws Exception {

    int res = 0;

    /**
     * 2-1. builder pattern 활용하여 Entity에 값을 세팅
     */
    UserEntity userEntity = UserEntity.builder()
            .userNo(userDTO.getUserNo())
            .userId(userDTO.getUserId())
            .userPw(EncryptUtil.encHashSHA256(userDTO.getUserPw()))
            .build();

    log.info("Service에서 로그인 체크 DTO 받은 Entity 값 = {}", userEntity);

    /**
     * 2-2. ID, PW 조회
     */
    res = checkLogin(userEntity);

    log.info("로그인 로직 체크 후 userENtity 값 = {}", userEntity);
    log.info("res = {}", res);
    return res;
}

private int checkLogin(UserEntity userEntity) {
    log.info(this.getClass().getName() + "로그인 체크 로직 실행");
    log.info("로그인 체크를 위해 받아온 비밀번호 값 = {}", userEntity.getUserPw());

    int res = 0;

    List<UserEntity> byPw = userRepository.checkLogin(userEntity.getUserId(),userEntity.getUserPw());

    if (byPw != null && !byPw.isEmpty() ) {
        log.info("res 1 ={}", byPw);
        res = 1;
    } else {
        log.info("res 0 ={}", byPw);
        res = 0;
    }

    return res;

}

dto 값을 builder에 전해주고 아래 ID, PW 체크를 위한 메소드를 만들어주고 보내주었다.

 

checkLogin에서는 구현체에서 받아온 userEntity를 userRepository로 보내주어 체크를 하고 받아온 값을 res로 로그인 정보를 담고 있는 경우와 아닌 경우를 나누었다.

 

public List<UserEntity> checkLogin(String userId, String userPw) {
        log.info("레포지토리에서 비밀번호 조회 시작");
        log.info("입력한 비밀번호 값 ={}", userPw);
        log.info("입력한 아이디 값 ={}", userId);
        log.info("체크 된 값 = {}", em.createQuery("select m from UserEntity m where m.userPw = :userPw and m.userId = :userId", UserEntity.class));
        log.info("레포지토리에서 비밀번호 조회 끝");
        return em.createQuery("select m from UserEntity m where m.userPw = :userPw and m.userId = :userId", UserEntity.class)
                .setParameter("userId", userId)
                .setParameter("userPw", userPw)
                .getResultList();
    }

체크 로직은 JPQL로 작성했다. 쿼리는 SQL 쿼리와 비슷하지만 Mapper에 적는 것과 양식이 다르다.

 

여기에서 체크된 값이 아래 코드

 

    if (byPw != null && !byPw.isEmpty() ) {
        log.info("res 1 ={}", byPw);
        res = 1;
    } else {
        log.info("res 0 ={}", byPw);
        res = 0;
    }

    return res;

를 통해 체크되고 res 값을 Controller로 반환해준다.

 

res = userService.login(userDTO);

log.info("res = {}", res);

if (res == 1) {
    log.info("res = {}", res);
    log.info("로그인 성공");
} else {
    log.info("res = {}", res);
    log.info("로그인 실패");
    return "user/login";
}

log.info(this.getClass().getName() + "로그인 로직 처리 끝");
userVo = null;

return "index";
}

Controller는 이렇게 받아온 res 값으로 1일 경우 로그인 성공을 1이 아닐 경우 로그인 실패로 다시 로그인 화면을 반환하는 식으로 코딩해주었다.

 

이렇게 thymeleaf를 사용하는 JPA builder Pattern을 통해 로그인 기능을 성공시켰다.

 

다만, 검증 부분과 아직 다듬어지지 못 한 곳들이 많다. 추후에 기능 완성을 하게 되면 글 수정을 해야한다.

 

로그는 중간마다 값 체크를 위해 찍어주었는데 이해를 돕기 위해 일단 놔둔다.

반응형

댓글