BE/Spring

[Spring] 로그인/회원가입

2sangmin 2023. 6. 21.

프로젝트를 하기전에 오랜만에 스프링의 돌아가는 형태를 다시 복습하기위해서 간단하게 로그인/ 회원가입 예제를 만들어 보았다.

 

 

회원가입

 

 

HTML 만들기

우선 Test를 위해 resoures에 있는 templates에다가 html을 만들어 주었다.

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    <h2>Hello Spring Boot!!</h2>
    <a href="/member/save">회원가입</a>
    <a href="/member/login">로그인</a>
</body>
</html>

 

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="/member/login" method="post">
    이메일: <input type="text" name="memberEmail"><br>
    비밀번호: <input type="password" name="memberPassword">
    <input type="submit" value="로그인">
</form>
</body>
</html>

 

 

save.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>save</title>
</head>
<body>
<!-- action 속성 : form에 작성한 데이터를 어디로 보낼지 지정-->
<form action="/member/save" method="post">
    <!-- name속성 : 서버로 전송할 때 변수 이름 -->
    이메일: <input type="text" name="memberEmail"><br>
    비밀번호: <input type="password" name="memberPassword"><br>
    이름: <input type="text" name="memberName"><br>
    <input type="submit" value="회원가입"> <!--sumbit 클릭시 form의 action주소로 Post요청하는개념임-->
</form>
</body>
</html>

 

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>main</title>
</head>
<body>
    session 값 확인: <p th:text="${session.loginEmail}"></p>
</body>
</html>

 

 

Controller 설정

회원가입을 만들기위해서 MemberController를 만들어야 한다. 회원가입을 하는 html은 위의 index.html에서 버튼을 클릭했을시 /member/save로 넘어가게 만들었기 때문에

 

MemberController에다가 /member/save를 띄우는 컨트롤러를 만들면 아래와 같다

@Controller //스프링 빈 등록
public class MemberController {

    // 회원가입 페이지 요청
    @GetMapping("/member/save")
    public String saveForm(){
        return "save";
    }
 }

이렇게만 해도 /member/save로 넘어간다.

 

다음은 이제 회원가입 기능을 만들기위해서 코드를 대폭 추가 해야한다. 우선 MemberController의 역할은 아래와 같다.

 

---> MemberController

  • Controller가 Service에 의존한다라고 표현
  • Service는 Repository를 의존
  • Service는 여러 Controller에서 가져다 쓸 수 있기 떄문에 Spring Container에 등록을 해야한다.

 

그럼, Controller와 Service, Repository, DTO, Entity를 만들어 주어야 한다. 

Controller부터 수정을 해주자.

@Controller //스프링 빈 등록
@RequiredArgsConstructor // 생성자 주입
public class MemberController {

    private final MemberService memberService;

    // 회원가입 페이지 요청
    @GetMapping("/member/save")
    public String saveForm(){
        return "save";
    }
    @PostMapping("/member/save")
    public String save(@ModelAttribute MemberDTO memberDTO) {
        System.out.println("MemberController.save");
        System.out.println("memberDTO = " + memberDTO);
        memberService.save(memberDTO);
        return "login";
    }
}

@Autowired를 사용한 필드 주입보단, @RequiredArgsConstructor(롬복 어노테이션)을 이용한 생성자 의존성 주입을 해준다 .

필드 주입은 spring 4.3부터 사용하지 않는것을 지양한다고 한다. 

 

생성자 주입을 사용할 경우 아래와 같은 장점이 있다고 합니다.

① 순환 참조 방지

② 테스트 코드 작성 용이

③ 코드 악취 제거

④ 객체 변이 방지 (final 가능)

 

위의 코드를 보면 save.html에서 버튼을 눌렀을때, @PostMapping이 작동이 된다. 

 

@ModelAttribute에 대한 글은 전에 포스팅 했다.

https://netco97.tistory.com/52

 

[Spring]Spring MVC - @ModelAttribute 장점 및 @RequestParam

@RequestParam Spring 에서 @PostMapping을 할 때, @PostMapping("/member/save") public String save(@RequestParam("memberEmail") String memberEmail, @RequestParam("memberPassword") String memberPassword, @RequestParam("memberName") String memberName) { Sy

netco97.tistory.com

 

memberService.save에 매개변수로 DTO를 준다.

 

MemberDTO

DTO란?

->DTO는 프로세스 간에 데이터를 전달하는 용도의 객체입니다. 비즈니스 로직을 포함하지 않는 데이터를 전달하기 위한 단순한 객체 를 뜻합니다. MVC 패턴에서는 주로 Client 와 Controller 사이에서 DTO 가 사용됩니다.

 

DTO코드를 보면,

MemberDTO

package com.example.member.dto;

import com.example.member.entity.MemberEntity;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class MemberDTO {
    private Long id;
    private String memberEmail;
    private String memberPassword;
    private String memberName;

}

와 같이 되어있다. 즉 save.html에서 내가 입력한 값들을 memberDTO라는 객체에다가 저장한다.

 

 

DB 연동, Entity

다음으로 우리는 데이터베이스와 연동을 해야하기 때문에 , Entity도 만들어줘야한다. 연동하기 전에 gradle설정과 application.yml 설정을 해야하는데 이걸 보고 따라하면 된다.

 

https://netco97.tistory.com/53

 

[Spring] Spring + MySQL 연동

의존성 등록 gradle에 2개를 추가해준다. implementation 'mysql:mysql-connector-java' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 근데 오류가 났다. 찾아보니깐 23년 4월부터 'mysql:mysql-connector-java'

netco97.tistory.com

 

위에나오는 Entity이다.

MemberEntity

package com.example.member.entity;

import com.example.member.dto.MemberDTO;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Setter
@Getter
@Table(name = "member_table")
public class MemberEntity {
    @Id //pk 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
    private Long id;

    @Column(unique = true) // unique 제약조건 추가
    private String memberEmail;

    @Column
    private String memberPassword;

    @Column
    private String memberName;

}

 

 

MemberRepository

Entity를 만들었으니,이제 Repository에서 Entity를 불러와야 하는데 이미 구현되어있는 JPA를 상속받아서 하면 된다.

 

package com.example.member.repository;

import com.example.member.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;


public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
    //첫번째 인자 = MemberEntity 두번째 인자 = Entity클래스의 pk가 어떤 타입인지

}

 

 

아까

 

---> MemberController

  • Controller가 Service에 의존한다라고 표현
  • Service는 Repository를 의존
  • Service는 여러 Controller에서 가져다 쓸 수 있기 떄문에 Spring Container에 등록을 해야한다.

를보면, 결국 Controller는 Service에 의존하고, Service는 다시 Repository를 의존한다는 의미가 이제 이해가 될 것이다. Repository는 다시 Entity Manager 역할을 한다.

 

대충 그려봤는데 이런 느낌이다. 그림으로만 봐서는 의미가 전달되지 않는것 같다 ㅋㅋ;

 

이제 회원을 저장하는 로직을 만들어야한다. 

 

MemberService 설정

 

가장먼저 Save를 하기위해서는

1. 객체를 전달하는 dto를 Entity로 변경해야한다.

2. repository의 save 메서드를 호출 해야한다.

 

이를 위해서 MemberEntity에  dto 를 Entity로 변경해주는 메서드를 만들어주어야 한다.

 

MemberEntity

package com.example.member.entity;

import com.example.member.dto.MemberDTO;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Setter
@Getter
@Table(name = "member_table")
public class MemberEntity {
    @Id //pk 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
    private Long id;

    @Column(unique = true) // unique 제약조건 추가
    private String memberEmail;

    @Column
    private String memberPassword;

    @Column
    private String memberName;

    public static MemberEntity toMemberEntity(MemberDTO memberDTO)//DTO to Entity
    {
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setMemberEmail(memberDTO.getMemberEmail());
        memberEntity.setMemberPassword(memberDTO.getMemberPassword());
        memberEntity.setMemberName(memberDTO.getMemberName());

        return memberEntity;
    }
}

 

 

그후에 MemberService에서 DTO를 Entity로 변경해주고, memberRepository에 있는 save는 JPARepository에 이미 구현되어있는 메서드이므로 그냥 불러오기만 하면된다. 이를 코드로 바꾸면 아래와 같다.

 

package com.example.member.service;

import com.example.member.dto.MemberDTO;
import com.example.member.entity.MemberEntity;
import com.example.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;
    public void save(MemberDTO memberDTO) {
        // 1. dto -> entity
        // 2. repository의 save 메서드 호출
        MemberEntity memberEntity = MemberEntity.toMemberEntity(memberDTO);
        memberRepository.save(memberEntity); //JPA가 제공하는 save라는 메서드가 이미 있음

        // repository의 save메서드 호출 JPA를 사용하므로 (조건. entity객체를 넘겨줘야 함)
    }
}

 

이렇게 하면

테이블에 잘 들어가있는 모습을 볼 수 있다.

 

로그인

다시 로그인 기능을 위해서는 MemberController에 추가해주어야 한다.

 

 

Controller 추가

@GetMapping("/member/login")
    public String loginForm(){
        return "login";
    }

@PostMapping("/member/login")
public String login(@ModelAttribute MemberDTO memberDTO, HttpSession session){
    MemberDTO loginResult = memberService.login(memberDTO);

    if(loginResult !=null){
        //login 성공
        session.setAttribute("loginEmail", loginResult.getMemberEmail());
        return "main";
    }
    else{
        //login 실패
        return "login";
    }
}

컨트롤러에 회원가입과 똑같이 /member/login으로 @GetMapping과 @PostMapping을 해준다. 이번에는 코드를 잘보면 DTO를 loginResult로 만들어서 memberService.login에서 받아온 값을 저장해서 비어있지 않을 경우에 성공으로 만들어 주었는데, 이를 잘보면 결국 Entity에서 DTO로 바꾸어서 보내준것이다. 이제 다시 MemberService부분으로 가기전에 이번에도 아까 DTO를 Entity로 바꾸어 주었던 부분을 Entity클래스에다가 만들었으니, 이번에는 반대로 Entity를 DTO로 바꾸기위해 DTO 클래스에다가 변환메서드를 만들어 준다.

 

MemberDTO

package com.example.member.dto;

import com.example.member.entity.MemberEntity;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class MemberDTO {
    private Long id;
    private String memberEmail;
    private String memberPassword;
    private String memberName;

    public static MemberDTO toMemberDTO(MemberEntity memberEntity){
        MemberDTO memberDTO = new MemberDTO();
        memberDTO.setId(memberEntity.getId());
        memberDTO.setMemberEmail(memberEntity.getMemberEmail());
        memberDTO.setMemberPassword(memberEntity.getMemberPassword());
        memberDTO.setMemberName(memberEntity.getMemberName());

        return memberDTO;
    }
}

 

 

이제 다만들었으니 Service에서 이를 이용하기만 하면 된다. 하지만 Sevice는 Repository에 의존한다고 했었는데, 로그인에서 이메일로 회원 정보를 조회하는 로직을 이용하기위해서 Repository에서 하나더 만들어줘야 한다.

 

MemberRepository

package com.example.member.repository;

import com.example.member.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
    //첫번째 인자 = MemberEntity 두번째 인자 = Entity클래스의 pk가 어떤 타입인지

    // 이메일로 회원 정보 조회(Select * from member_table where member=email=?)
    Optional<MemberEntity> findByMemberEmail(String memberEmail); //Optional -> null 방지
}

 

 

MemberService

package com.example.member.service;

import com.example.member.dto.MemberDTO;
import com.example.member.entity.MemberEntity;
import com.example.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;
    public void save(MemberDTO memberDTO) {
        // 1. dto -> entity
        // 2. repository의 save 메서드 호출
        MemberEntity memberEntity = MemberEntity.toMemberEntity(memberDTO);
        memberRepository.save(memberEntity); //JPA가 제공하는 save라는 메서드가 이미 있음

        // repository의 save메서드 호출 JPA를 사용하므로 (조건. entity객체를 넘겨줘야 함)
    }

    public MemberDTO login(MemberDTO memberDTO) {
        /*
            1. 회원이 입력한 이메일로 DB에서 조회를 함
            2. DB에서 조회한 비밀번호 사용자가 입력한 비밀번호가 일치하는지 판단
         */
        Optional<MemberEntity> byMemberEmail = memberRepository.findByMemberEmail(memberDTO.getMemberEmail());
        if(byMemberEmail.isPresent()){
            // 조회 결과가 있다(해당 이메일을 가진 회원정보가 있다)
            MemberEntity memberEntity = byMemberEmail.get(); //Optional.get메서드를 통해 Entity객체를 가져올수 있다.
            if(memberEntity.getMemberPassword().equals(memberDTO.getMemberPassword())){
                // 비밀번호 일치
                // entity -> DTO 변환 후 리턴
                MemberDTO dto = MemberDTO.toMemberDTO(memberEntity);
                return dto;
            }
            else{
                //비밀번호 불일치
                return null;
            }
        }
        else{
            // 조회 결과가 없다
            return null;
        }
    }
}

주석을 잘보면 Login도 어떻게 구현했는지 이해가 갈 것이다. 내가 입력한(DTO) 이메일을 조회한 후, 있으면 -> Entity(회원가입한 데이터베이스)의 비밀번호와 내가 입력한 (DTO) 비밀번호를 비교해서 일치할 경우 entity를 다시 DTO로 변환해서 return 해주면, 컨트롤러에서 처음에 만들었던 

if(loginResult !=null){
            //login 성공
            session.setAttribute("loginEmail", loginResult.getMemberEmail());
            return "main";
        }

이부분에 의해서 session정보로 가지고 있게 된다. 

 

글이 길지만 나중에 다시 복습할때를 위해서 조금 자세하게 적어놨다. 

댓글