세션을 사용하여 로그인(아이디 기억하기) 및 로그아웃하기
들어가며
이번 포스팅에서는 세션을 사용하여 로그인상태를 유지하고 사용자권한별로 접속하는 페이지가 다르게 하며 로그아웃하는 기능을 개발해 보겠습니다.
Repository
- 이번 로그인에서도 JpaRepositroy를 사용하지 않고 직접 개발하여 사용하도록 하겠습니다
- Reposiotry를 인터페이스로 만들어 오버라이딩하여 사용하도록 하겠습니다.
LoginRepository
public interface LoginRepository {
List<Member> findById(String id);
List<Member> findBPwd(String pwd);
}
- findById(String id) : 객체에서 아이디값을 찾는 메서드
- findByPwd(String pwd) : 객체에서 비밀번호 값을 찾는 메서드
loginRepository를 상속받는 loginRepositoryImpl을 생성하여 작성합니다.
LoginRepositoryImpl
@Repository
@RequiredArgsConstructor
public class LoginRepositoryImpl implements LoginRepository{
private final EntityManager em;
@Override
public List<Member> findById(String id) {
String jpql = "select m from Member m where m.loginId=:loginId";
List<Member> loginId = em.createQuery(jpql, Member.class).setParameter("loginId", id).getResultList();
return loginId;
}
@Override
public List<Member> findBPwd(String pwd) {
String jpql = "select m from Member m where m.password1 = :password";
List<Member> password = em.createQuery(jpql, Member.class).setParameter("password", pwd).getResultList();
return password;
}
}
EntityManager을 사용하여 직접 파라미터형식으로 값을 얻어 오는 쿼리문을 작성합니다.
Service
이제는 로그인서비스 기능을 제공하는 LoginSerivce클래스를 만들어 코드를 작성합니다.
LoginService
@Service
@Transactional
@RequiredArgsConstructor
public class LoginService {
private final LoginRepositoryImpl loginRepository;
public Member login(Member member){
List<Member> loginId = loginRepository.findById(member.getLoginId());
List<Member> password = loginRepository.findBPwd(member.getPassword1());
if(loginId.isEmpty() ||password.isEmpty() || !member.getPassword1().equals(password.get(0).getPassword1())){
return null;
}
return loginId.get(0);
}
}
- Member login(Member member) : findbyId()와 findByPwd()를 실행하여 Member에서 값을 가져옵니다. 그 후에 값이 비어있거나 입력한 비밀번호와 저장된 비밀번호값이 일치하지 않으면 null 값을 반환합니다. 일치할 경우에는 loginId의 값을 반환합니다.
Session
이제는 세션을 이용하여 로그인을 할 경우에 세션에 값을 저장하여 로그인 상태를 유지하도록 할 것입니다. 직접 세션을 만들어서 사용할 수도 있지만 스프링에서 제공하는 세션을 사용하여 구현해 보도록 하겠습니다.
SessionConst
public class SessionConst {
public static final String LOGIN_MEMBER = "loginMember";
}
- SessionConst클래스에 LOGIN_MEMBER라는 상수 문자열을 정의했습니다. 이 상수는 세션에서의 로그인한 사용자의 KEY값으로 사용될 것입니다. 그래서 키값을 통해 검색하고 저장할 수 있으며, 오타 줄여 안정적으로 사용할 수 있습니다.
Controller
컨트롤로 부분에서는 로그인매핑과 로그아웃을 구현해 보도록 하겠습니다. 기존 LoginController에 loginGet매핑한 곳에 @ModerAttribute("member") Member member을 추가해 줍니다. 그리고 앞에서 만든 login 메서드를 사용하기 위해서
private final LoginService loginService; 한 줄 작성합니다.
LoginController
loginPost
@PostMapping("/login")
public String loginPost(@Valid @ModelAttribute("member")Member member, BindingResult result, HttpServletRequest request){
Member loginMember = loginService.login(member);
if(loginMember==null){
result.rejectValue("password1","MisMath","아이디 또는 비밀번호가 일치하지 않습니다.");
}
if (result.hasErrors()){
return "login/loginPage";
}
HttpSession session = request.getSession();
session.setAttribute(SessionConst.LOGIN_MEMBER,loginMember);
return "redirect:/"+ loginMember.getRole();
}
loginService.login(member)을 통해서 loginMember변수를 생성합니다. 만약에 일치하는 값이 일치하지 않으면 password1 부분에 오류 메시지를 나오도록 했으며 session에 LOGIN_MEMBER키를 통해서 loginMember값을 넣어주며
로그인 성공 시 loginMember에서 권한에 맞게 홈페이지를 이동하게 했습니다.
logout
@PostMapping("/logout")
public String logout(HttpServletRequest request){
HttpSession session = request.getSession(false);
if(session!=null){
session.invalidate();
}
return "redirect:/";
}
로그아웃은 세션의 값이 들어있다면 세션을 삭제해주기만 하면 로그아웃을 구현할 수 있습니다.
HomeController
권한에 맞는 페이지에 접속할 수 있도록 매핑정보를 작성합니다.
loginHome
@GetMapping("/{role}")
public String loginHoe(HttpServletRequest request, @PathVariable("role") String role, Model model){
HttpSession session = request.getSession(false);
if(session==null ){
return "home";
}
Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
if(loginMember==null ){
return "home";
}
if(role.equals(String.valueOf(user))){
model.addAttribute("member", loginMember);
return "userHome";
}
model.addAttribute("member",loginMember);
return "loginHome";
}
- HttpSession session = request.getSession(false); : 세션에 이미 값이 있으면 새로운 세션을 반환하지 않고 기존에 있는 세션을 반환
- 로그인에서 권한 값이 넘어온 걸 비교하여 user일 때와 admin일때 model에 loginMember을 넣어준다
HTML
기존에 작성했던 header을 이용하여 권한이 admin, user일때 보여주는 페이지를 작성하며, 로그인 페이제에서 아이디 기억하는 체크박스를 정말 간단하게 구현해 볼 예정입니다. (이 프로젝트에서는 기본적으로 admin을 메인으로 했습니다.)
권한별로 로그인 페이지 화면(admin 기준)
상품등록은 admin만 가능하게 개발할 예정
※ user일 경우
userheader.html
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle" th:if="${member != null}" th:text="|${member.username}|" role="button" data-bs-toggle="dropdown" aria-expanded="false">로그인 사용자이름</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" th:text="|${member.role}|">권한</a> </li>
<li><a class="dropdown-item" th:href="@{'/'+${member.role}+'/myInfor'}">내정보</a><li>
<li><hr class="dropdown-divider" /> </li>
<li>
<form th:action="@{/logout}" method="post">
<button type="submit" class="dropdown-item">로그아웃</button>
</form>
</li>
</ul>
</div>
header 부분을 복사하여 userheader로 만든 후 수정
userloginlay.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>책방</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
<!-- Bootstrap icons-->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" rel="stylesheet" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="/css/styles.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<style>
@font-face {
font-family: 'DungGeunMo-Regular';
src: url('https://cdn.jsdelivr.net/gh/wooin21/web/fonts/etc/DungGeunMo-Regular.woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'DungGeunMo-Regular';
}
.item .card:hover {border: #0D0D0D 1px solid;}
</style>
<!-- 각 페이지의 script 가 작성될 위치-->
<th:block layout:fragment="script"></th:block>
<!-- 각 페이지의 css 가 작성될 위치-->
<th:block layout:fragment="css"></th:block>
</head>
<body>
<div th:replace="fragements/frage/userheader :: userheader"></div>
<div layout:fragment="content" class="content"></div>
<div th:replace="fragements/frage/footer ::footer"></div>
</body>
</html>
userHome.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{/fragements/layout/userloginlay}">
<div layout:fragment="content">
</div>>
</html>
/templates 하위에다가 생성한다.
※ admin일 경우
loginheader.html
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle" th:if="${member != null}" th:text="|${member.username}|" role="button" data-bs-toggle="dropdown" aria-expanded="false">로그인 사용자이름</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" th:text="|${member.role}|">권한</a> </li>
<li><a class="dropdown-item" th:href="@{'/'+${member.role}+'/myInfor'}">내정보</a><li>
<li><hr class="dropdown-divider" /> </li>
<li><a class="dropdown-item" href="/admin/items/new">상품등록</a> </li>
<li>
<form th:action="@{/logout}" method="post">
<button type="submit" class="dropdown-item">로그아웃</button>
</form>
</li>
</ul>
</div>
loginlay.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>책방</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
<!-- Bootstrap icons-->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" rel="stylesheet" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="/css/styles.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<style>
@font-face {
font-family: 'DungGeunMo-Regular';
src: url('https://cdn.jsdelivr.net/gh/wooin21/web/fonts/etc/DungGeunMo-Regular.woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'DungGeunMo-Regular';
}
.item .card:hover {border: #0D0D0D 1px solid;}
</style>
<!-- 각 페이지의 script 가 작성될 위치-->
<th:block layout:fragment="script"></th:block>
<!-- 각 페이지의 css 가 작성될 위치-->
<th:block layout:fragment="css"></th:block>
</head>
<body>
<div th:replace="fragements/frage/loginheader :: loginHeader"></div>
<div layout:fragment="content" class="content"></div>
<div th:replace="fragements/frage/footer ::footer"></div>
</body>
</html>
loginHome
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{/fragements/layout/loginlay}">
<div layout:fragment="content">
</div>>
</html>
위처럼 각 권한에 맞는 페이지를 작성해 줍니다.
이번에는 로그인페이지에서 Rember Id체크박스 클릭시 아이디를 기억하게 하는 기능과 아이디와 비밀번호 일치하지 않을시 오류메시지를 표시하도록 해보겠습니다.
컨트롤러에서 오류메세지를 저장했던 값을 나오게 하기위해서는 password1필드 부분에
<div th:if="${#fields.hasErrors('password1')}" class="text-danger text-center">아이디또는 비밀번호가 일치하지 않습니다.</div>
코드 한줄을 추가합니다. 이코드는 오류가 발생시에만 표시되게합니다.
아이디 기억하는 기능은 아래와같이 스크립트를 추가합니다.(이기능은 간단하게 구현한 기능이라.. 수정을 좀 해야합니다............................)
document.addEventListener("DOMContentLoaded", function() {
// 아이디 기억 체크박스 요소
var rememberIdCheck = document.getElementById("rememberIdCheck");
// 아이디를 로컬 스토리지에서 가져옵니다.
var savedId = localStorage.getItem("savedId");
// 저장된 아이디가 있을 경우, 아이디를 채워 넣고 체크박스를 선택합니다.
if (savedId) {
document.querySelector('input[type="text"]').value = savedId;
rememberIdCheck.checked = true;
}
// 아이디 기억 체크박스의 클릭 이벤트 처리
rememberIdCheck.addEventListener("click", function() {
var idInput = document.querySelector('input[type="text"]');
if (rememberIdCheck.checked) {
// 체크박스가 선택된 경우, 아이디를 로컬 스토리지에 저장합니다.
localStorage.setItem("savedId", idInput.value);
} else {
// 체크박스가 선택되지 않은 경우, 로컬 스토리지에서 아이디를 제거합니다.
localStorage.removeItem("savedId");
}
});
});
실행
로그인 기능이 올바르게 실행되며 url에도 admin으로 접속되다. 일치하지 않다면 오류메시지가 정상적으로 뜨는것도 확
인해볼수있다.
다음으로
다음에는 아이디 찾기와 비밀번호 찾기는 한번 구현해보겠습니다.!!
'ToyProject쇼핑몰' 카테고리의 다른 글
[스프링 부트] JPA 쇼핑몰 # 11. 상품등록 (0) | 2023.11.05 |
---|---|
[스프링 부트] JPA 쇼핑몰 # 10.아이디찾기(본인인증), 비밀번호 찾기 (1) | 2023.10.31 |
[스프링 부트] JPA 쇼핑몰 # 8.로그인 레이아웃 (0) | 2023.10.27 |
[스프링 부트] JPA 쇼핑몰 # 7.회원가입 실행해보기 (0) | 2023.10.27 |
[스프링 부트] JPA 쇼핑몰 # 6.회원가입 주소 (0) | 2023.10.27 |