본 프로젝트 자료는 김영한님의 스프링 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 클래스 생성
편의상 테스트용 회원 데이터를 추가하자.
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
본인이 개발하고 싶은 쿠키로 개발해서 적용할 때 주의하도록 하자.
LoginController - login()
로그인 클래스에 로그인 성공시 세션 쿠키를 생성해주는 로직 추가
로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 담는다. 쿠키이름 memberId이고, 값은 회원의 id 를 담는다. 웹 종료 시 까지 유지시켜준다.
로그인 처리
로그아웃 기능
세션 쿠키이므로 웹 브라우저 종료 시 서버에서 해당 쿠키의 종료 날짜를 0으로 지정
이제 작업이 끝났으니 HTMl 로그인 성공한 유저의 화면을 만들어보자.
HTML
로그인 성공 화면
로그인 성공하면 다음과 같은 화면이 출력된다.
하지만 이렇게 단순하게 만들면 보안에 취약한 문제점이 생긴다.
보안 문제는 2편에 이어 작성