[Spring] 로그인 및 회원가입 제작(1)

JJoSuk·2023년 6월 9일
0

본 프로젝트 자료는 김영한님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 참고 제작됐음을 알립니다.

로그인 기능을 제작하기 전에 홈 화면을 만들려고 한다.

홈 화면

HTML

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>홈 화면</h2>
    </div>
    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg" type="button"
                    th:onclick="|location.href='@{/members/add}'|">
                회원 가입
            </button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-dark btn-lg"
                    onclick="location.href='items.html'"
                    th:onclick="|location.href='@{/login}'|" type="button">
                로그인
            </button>
        </div>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

실행 결과

localHost:8080 입력 하면 현재 화면을 확인 할 수 있다.

화면 속 회원가입과 로그인을 제작해볼려고 한다.


회원 가입

Member 클래스 생성

MemberRepository 클래스 생성

@Slf4j
@Repository
public class MemberRepository {

    private static Map<Long, Member> store = new HashMap<>(); // static 사용
    private static long sequense = 0L;

    public Member save(Member member) {
        member.setId(++sequense);
        log.info("save: member={}", member);
        store.put(member.getId(), member);
        return member;
    }

    public Member findById(Long id) {
        return store.get(id);
    }

    public Optional<Member> findByLoginId(String loginId) {
        return findAll().stream()
                .filter(m -> m.getLoginId().equals(loginId))
                .findFirst();
    }

    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore() {
        store.clear();
    }
}

MemberController 클래스 생성

회원용 테스트 데이터 추가

편의상 테스트용 회원 데이터를 추가하자.

  • loginId : test
  • password : test!
  • name: 테스터

HTML 은 전에 만든걸 재활용 해보자.

실행 결과


로그인 기능

로그인 기능을 개발해보자.

LoginService 클래스 생성

로그인 했을 때 넘어온 password 를 비교해 일치하지 않으면 null 로 반환.

LoginForm 클래스 생성

로그인 로직

LoginController 클래스 생성

로그인 서비스를 호출해서 로그인에 성공하면 홈 화면으로 이동하고,

로그인에 실패하면 bindingResult.reject() 를 사용해서 글로벌 오류( ObjectError )를 생성한다.

loginForm.html 뷰 템플릿 생성

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
          .container {
              max-width: 560px;
          }
          .field-error {
              border-color: #dc3545;
              color: #dc3545;
          }
      </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>로그인</h2>
    </div>
    <form action="item.html" th:action th:object="${loginForm}" method="post">
        <div th:if="${#fields.hasGlobalErrors()}">
            <p class="field-error" th:each="err : ${#fields.globalErrors()}"
               th:text="${err}">전체 오류 메시지</p>
        </div>
        <div>
            <label for="loginId">로그인 ID</label>
            <input type="text" id="loginId" th:field="*{loginId}" class="form-control"
                   th:errorclass="field-error">
            <div class="field-error" th:errors="*{loginId}" />
        </div>
        <div>
            <label for="password">비밀번호</label>
            <input type="password" id="password" th:field="*{password}"
                   class="form-control" th:errorclass="field-error">
        <div class="field-error" th:errors="*{password}" />
    </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">로그인</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'"
                        th:onclick="|location.href='@{/}'|" type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

정보를 다시 입력하도록 로그인 폼을 뷰 템플릿으로 사용한다.

실행 결과


로그인 처리하기 - 쿠키 사용

로그인을 해도 다른 장소로 넘어갈 경우 초기화가 되어 로그인 정보가 풀린다. 그래서 쿠키라는걸 사용해서 로그인 상태 유지할려고 한다.

작동원리

쿠키 생성

클라이언트 쿠키 전달1

클라이언트 쿠키 전달2

쿠키에는 2가지 종류가 있다.

  • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
  • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

본인이 개발하고 싶은 쿠키로 개발해서 적용할 때 주의하도록 하자.

쿠키 적용 방법

LoginController - login()

로그인 클래스에 로그인 성공시 세션 쿠키를 생성해주는 로직 추가

로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 담는다. 쿠키이름 memberId이고, 값은 회원의 id 를 담는다. 웹 종료 시 까지 유지시켜준다.

로그인 처리

  • @CookieValue 를 사용하면 편리하게 쿠키를 조회할 수 있다.
  • 로그인 하지 않은 사용자도 홈에 접근할 수 있기 때문에 required = false 를 사용한다.
  • 로그인 쿠키나 id가 없으면 home 으로 보낸다.
  • 로그인 쿠키( memberId )가 있는 사용자는 로그인 사용자 전용 홈 화면인 loginHome 으로 보낸다.

로그아웃 기능

세션 쿠키이므로 웹 브라우저 종료 시 서버에서 해당 쿠키의 종료 날짜를 0으로 지정

이제 작업이 끝났으니 HTMl 로그인 성공한 유저의 화면을 만들어보자.

HTML

로그인 성공 화면

로그인 성공하면 다음과 같은 화면이 출력된다.

하지만 이렇게 단순하게 만들면 보안에 취약한 문제점이 생긴다.

보안 문제는 2편에 이어 작성

profile
안녕하세요

0개의 댓글