2023. 12. 20. 23:16ㆍ학부 강의/웹프로그래밍 (Spring)
0. 출처
아직 배우고 있는 중이라 부정확한 정보가 포함되어 있을 수 있습니다!
주의하세요!
올인원 스프링 프레임워크 참고.
1. 로그인 화면
이전에 관리자 회원가입 기능을 구현했다.
최고 관리자가 아닌 일반 관리자의 경우 회원가입 후 최고 관리자의 승인 이후에 로그인이 가능하다.
관리자 목록을 출력하고 로그인을 승인하는 기능을 구현하고 로그인까지 구현해 보자.
<li><a href="<c:url value='/admin/member/loginForm' />">로그인</a></li>
admin/include/nav.jsp
를 보면 로그인 버튼을 누르면 클라이언트에서 보내는 url을 알 수 있다.
@Controller
@RequestMapping("/admin/member")
public class AdminMemberController {
...
@GetMapping("/loginForm")
public String loginForm() {
System.out.println("[AdminMemberController] loginForm()");
String nextPage = "admin/member/login_form";
return nextPage;
}
}
AdminMemberController
가 /admin/member/loginForm
에 대한 HTTP GET 요청을 처리하는 Method를 추가한다.
/admin/member/loginForm
로그인 화면이다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<jsp:include page="../../include/title.jsp" />
<link href="<c:url value='/resources/css/admin/login_form.css' />" rel="stylesheet" type="text/css">
<jsp:include page="../include/login_js.jsp" />
</head>
<body>
<jsp:include page="../../include/header.jsp" />
<jsp:include page="../include/nav.jsp" />
<section>
<div id="section_wrap">
<div class="word">
<h3>LOGIN FORM</h3>
</div>
<div class="login_form">
<form action="<c:url value='/admin/member/loginConfirm' />" name="login_form" method="post">
<input type="text" name="a_m_id" placeholder="INPUT ADMIN ID."> <br>
<input type="password" name="a_m_pw" placeholder="INPUT ADMIN PW."> <br>
<input type="button" value="login" onclick="loginForm();">
<input type="reset" value="reset">
</form>
</div>
<div class="find_password_create_account">
<a href="<c:url value='/admin/member/findePasswordForm' />">find password</a>
<a href="<c:url value='/admin/member/createAccountForm' />">create account</a>
</div>
</div>
</section>
<jsp:include page="../../include/footer.jsp" />
</body>
</html>
WEB-INF/views/admin/member/login_form.jsp
이다.
...
<form action="<c:url value='/admin/member/loginConfirm' />" name="login_form" method="post">
<input type="text" name="a_m_id" placeholder="INPUT ADMIN ID."> <br>
<input type="password" name="a_m_pw" placeholder="INPUT ADMIN PW."> <br>
<input type="button" value="login" onclick="loginForm();">
<input type="reset" value="reset">
</form>
아이디 정보를 a_m_id
으로, 비밀번호를 a_m_pw
라는 이름으로 서버에 전송한다.
서버로 /admin/member/loginConfirm
url로 POST 요청을 보낸다.
2. 로그인 기능 구현
클라이언트로부터 받은 /admin/member/loginConfirm
요청을 처리해 보자.
가. 컨트롤러
@Controller
@RequestMapping("/admin/member")
public class AdminMemberController {
...
@PostMapping("/loginConfirm")
public String loginConfirm(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberController] loginConfirm()");
String nextPage = "admin/member/login_ok";
AdminMemberVo loginedAdminMemberVo = adminMemberService.loginConfirm(adminMemberVo);
if(loginedAdminMemberVo == null) {
nextPage = "admin/member/login_ng";
}
return nextPage;
}
}
AdminMemberController
에 /admin/member/loginConfirm
POST 요청을 처리하는 loginConfirm()
추가한다.
adminMemberService.loginConfirm(adminMemberVo);
의 리턴값에 따라서 다른 view 정보를 반환한다.
loginedAdminMemberVo
가 null
이면 로그인에 실패한 것이다.
나. 서비스
@Service
public class AdminMemberService {
...
public AdminMemberVo loginConfirm(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberService] loginConfirm()");
AdminMemberVo loginedAdminMemberVo = adminMemberDao.selectAdmin(adminMemberVo);
if(loginedAdminMemberVo != null)
System.out.println("[AdminMemberService] ADMIN MEMBER LOGIN SUCCESS!!");
else
System.out.println("[AdminMemberService] ADMIN MEMBER LOGIN FAIL!!");
return loginedAdminMemberVo;
}
}
AdminMemberService.loginConfirm()
에서는 AdminMemberController
에게서 로그인 정보를 전달받아서 올바른 접근인지 확인한다.
다. DAO
@Component
public class AdminMemberDao {
...
public AdminMemberVo selectAdmin(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberDao] selectAdmin()");
String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_id = ? AND a_m_approval > 0";
List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();
try {
adminMemberVos = jdbcTemplate.query(sql, new RowMapper<AdminMemberVo>() {
@Override
public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
AdminMemberVo adminMemberVo = new AdminMemberVo();
adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
adminMemberVo.setA_m_id(rs.getString("a_m_id"));
adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
adminMemberVo.setA_m_name(rs.getString("a_m_name"));
adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
adminMemberVo.setA_m_part(rs.getString("a_m_part"));
adminMemberVo.setA_m_position(rs.getString("a_m_position"));
adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
return adminMemberVo;
}
}, adminMemberVo.getA_m_id());
if(!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
adminMemberVos.clear();
} catch (Exception e) {
e.printStackTrace();
}
return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}
}
데이터베이스에 전달받은 id와 같은 데이터를 찾고 비밀번호를 확인한다.
List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();
: SQL의 결괏값이 여러 개일 수 있음.String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_id = ? AND a_m_approval > 0";
: 전달받은 아이디와 동일한 데이터를 데이버베이스로부터 조회한다.
: 최고 관리자의 승인을 받아야 로그인할 수 있다.if(!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw())) adminMemberVos.clear();
: 해쉬화된 비밀번호와 비교하여 올바른 비밀번호를 입력했는지 확인한다.return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
: 로그인에 성공하면AdminMemberVo
를 반환한다.
: 로그인에 실패하면null
을 반환한다.
1) RowMapper
SELECT
문을 실행하기 위해서 jdbcTemplate.query()
을 사용해야 한다.
이때 query(String sql, RowMapper rowMapper)
는 주어진 SQL문의 결과를 객체로 매핑해야 한다.
결괏값을 매핑하는 기능을 구현하기 위해선 RowMapper.mapRow()
라는 추상 메서드를 구현해야 한다.
public [return type] mapRow(ResultSet rs, int rowNum)
:rs
는 SQL의 결괏값이 저장된 데이터셋이다.
:rowNum
은rs
중 몇 번째 값을 매핑하고 있는지 나타낸다.
여기서는 adminMemberVo
의 setter를 사용해서 데이터를 매핑한다.
매핑이 마무리되면 데이터가 저장된 adminMemberVo
를 반환한다.
라. 로그인 시도
최고 관리자 로그인 성공.
일반 관리자 admin1을 생성한다.
일반 관리자 로그인 실패.
일반 관리자는 최고 관리자의 승인이 있어야지 로그인이 가능하다.
미승인 관리자의 경우 tbl_admin_member
테이블을 보면 a_m_approval
이 0인 것을 볼 수 있다.
3. 로그인 상태 유지 & 로그아웃 구현
로그인 상태를 유지하는 기능을 구현한다.
이전에 Node.js에서 공부한 내용을 정리한 글.
가. 쿠키와 세션
웹 서버의 부하를 줄이기 위해서 HTTP 프로트콜은 비연결성 프로토콜이다.
연결을 유지하지 않기 때문에 HTTP는 클라이언트와 서버의 상태를 유지하지 않는 Stateless 한 특성을 지닌다.
로그인 상태를 유지하기 위해서 쿠키, 세션, JWT와 같은 기술을 사용한다.
1) 쿠키 실습
package com.company.cookie;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/member")
public class MemberComtroller {
...
@PostMapping("/loginConfirm")
public String loginConfirm(@RequestParam("m_id") String m_id, @RequestParam("m_pw") String m_pw, HttpServletResponse response) {
System.out.println("[MemberController] loginConfirm()");
String nextPage = "member/login_ok";
if(m_id.equals("user") && m_pw.equals("1234")) {
Cookie cookie = new Cookie("loginMember", m_id);
cookie.setMaxAge(60 * 30);
response.addCookie(cookie);
}else {
nextPage = "member/login_ng";
}
return nextPage;
}
@GetMapping("/logoutForm")
public String logoutForm(@CookieValue(value = "loginMember", required = false) String loginMember, HttpServletResponse response) {
System.out.println("[MemberController] logoutForm()");
String nextPage = "redirect:/member";
Cookie cookie = new Cookie("loginMember", loginMember);
cookie.setMaxAge(0);
response.addCookie(cookie);
return nextPage;
}
}
쿠키를 생성하고 쿠키를 제거하는 간단한 예시다.
Cookie cookie = new Cookie("loginMember", m_id);
: 새로운 쿠키를 생성한다.
: 이름은loginMember
, 값은m_id
의 값을 대입한다.cookie.setMaxAge(60 * 30);
: 쿠키의 유효 시간을 설정한다.
: 시간의 기본 단위는 초다. (60 * 30 * 1초 = 30분)cookie.setMaxAge(0);
: 쿠키의 유효 시간을 0으로 설정한다.response.addCookie(cookie);
: 쿠키를 클라이언트에게 전달.public String logoutForm(@CookieValue(value = "loginMember", required = false) String loginMember, HttpServletResponse response) {
: 서버에서 쿠키를 전달받는 방법.
:value
는 사용할 쿠키의 이름을 입력한다.
:required
는 필수 요소인지 표시한다.false
면 없어도 에레가 발생하지 않음.
: 쿠키의 값은String
타입으로loginMember
에 저장된다.
2) 세션
AdminMemberDao.selectAdmin()
에선 로그인에 성공하면 계정의 정보가 담긴 AdminMemberVo를 반환하는데 이를 세션에 저장해 보겠다.
//AdminMemberController.java
...
@PostMapping("/loginConfirm")
public String loginConfirm(AdminMemberVo adminMemberVo, HttpSession session) {
System.out.println("[AdminMemberController] loginConfirm()");
String nextPage = "admin/member/login_ok";
AdminMemberVo loginedAdminMemberVo = adminMemberService.loginConfirm(adminMemberVo);
if(loginedAdminMemberVo == null) {
nextPage = "admin/member/login_ng";
}else {
session.setAttribute("loginedAdminMemberVo", loginedAdminMemberVo);
session.setMaxInactiveInterval(60 * 30);
}
return nextPage;
}
AdminMemberController
에서 세션에 AdminMemberVo
를 저장하도록 loginConfirm()
를 수정한다.
public String loginConfirm(AdminMemberVo adminMemberVo, HttpSession session) { … }
:loginConfirm()
의 매개변수에HttpSession
추가.
: 요청을 보낸 클라이언트의 세션에 접근할 수 있음.session.setAttribute("loginedAdminMemberVo", loginedAdminMemberVo);
:setAttribute(String name, Object value) : void
: 타입이Object
이기 때문에 모든 데이터 타입을 저장 가능.
: 세션에loginedAdminMemberVo
이란 이름으로loginAdminMemberVo
저장session.setMaxInactiveInterval(60 * 30);
: 기본 단위 초. (60 * 30 * 초 = 30분)
: 설정한 시간 동안 아무런 동작이 없으면 세션이 종료되고 다시 로그인해야 함.
<%@page import="com.office.library.admin.member.AdminMemberVo"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<link href="<c:url value='/resources/css/admin/include/nav.css' />" rel="stylesheet" type="text/css">
<jsp:include page="./nav_js.jsp" />
<nav>
<div id="nav_wrap">
<%
AdminMemberVo loginedAdminMemberVo = (AdminMemberVo) session.getAttribute("loginedAdminMemberVo");
if (loginedAdminMemberVo != null) {
%>
<div class="menu">
<ul>
<li><a href="<c:url value='/admin/member/logoutConfirm' />">로그아웃</a></li>
<li><a href="<c:url value='/admin/member/modifyAccountForm' />">계정수정</a></li>
<c:if test="${loginedAdminMemberVo.a_m_id eq 'super admin'}">
<li><a href="<c:url value='/admin/member/listupAdmin' />">관리자목록</a></li>
</c:if>
<li><a href="<c:url value='/book/admin/getRentalBooks' />">대출도서</a></li>
<li><a href="<c:url value='/book/admin/getAllBooks' />">전체도서</a></li>
<li><a href="<c:url value='/book/admin/getHopeBooks' />">희망도서(입고처리)</a></li>
<li><a href="<c:url value='/book/admin/registerBookForm' />">도서등록</a></li>
</ul>
</div>
<%
} else {
%>
<div class="menu">
<ul>
<li><a href="<c:url value='/admin/member/loginForm' />">로그인</a></li>
<li><a href="<c:url value='/admin/member/createAccountForm' />">회원가입</a></li>
</ul>
</div>
<%
}
%>
<div class="search">
<form action="<c:url value='/book/admin/searchBookConfirm' />" name="search_book_form" method="get">
<input type="text" name="b_name" placeholder="Enter the name of the book you are looking for.">
<input type="button" value="search" onclick="searchBookForm();">
</form>
</div>
</div>
</nav>
admin/include/nav.jsp
에서 세션의 loginAdminMemberVo
속성값이 null
인지 아닌지 확인한다.
null
이면 로그인, 회원가입 링크를, null
이 아니면 로그아웃, 계정수정, 관리자목록 등 링크를 보여준다.
나. 로그아웃
//AdminMemberController.java
...
@GetMapping("/logoutConfirm")
public String logoutConfirm(HttpSession session) {
System.out.println("[AdminMemberController] logoutConfirm()");
String nextPage = "redirect:/admin";
session.invalidate();
return nextPage;
}
AdminMemberController
에 logoutConfirm()
추가.
String nextPage = "redirect:/admin";
:/admin
으로 리다이렉트session.invalidate();
: 세션 무효화.
: 세션에 저장된 데이터(예: 사용자 인증 정보, 사용자 설정 등)가 모두 삭제.
: 서버에 할당된 자원을 해제. 불필요한 메모리 사용을 줄이는 데 도움이 됨.
1) 주요 HttpSession 기능
메서드 | 목적 | 설명 |
getId() | 세션 ID 획득 | 현재 HTTP 세션의 고유 식별자를 반환합니다. |
getAttribute(String name) | 속성 조회 | 지정된 이름의 객체를 세션에서 반환합니다. |
setAttribute(String name, Object value) | 속성 설정 | 세션에 객체를 저장합니다. 이름과 값으로 구성됩니다. |
removeAttribute(String name) | 속성 제거 | 세션에서 지정된 이름의 객체를 제거합니다. |
invalidate() | 세션 무효화 | 현재 세션을 종료하고, 모든 속성을 제거합니다. |
isNew() | 새 세션 확인 | 현재 세션 객체가 새로 생성되었는지 여부를 반환합니다. |
setMaxInactiveInterval(int second) | 최대 비활성화 간격 설정 | 세션의 최대 비활성화 시간(초 단위)을 설정합니다. |
getMaxInactiveInterval() | 최대 비활성화 간격 조회 | 세션의 최대 비활성화 시간을 반환합니다. |
getCreationTime() | 생성 시간 조회 | 세션 생성 시간을 반환합니다. |
getLastAccessedTime() | 마지막 접근 시간 조회 | 세션에 마지막으로 접근한 시간을 반환합니다. |
4. 최고 관리자 승인 처리 기능 구현
최고 관리자가 일반 관리자 목록을 조회하고 특정 일반 관리자의 로그인을 승인하는 기능을 구현해 보자.
가. 관리자 목록 조회하기
1) 컨트롤러
@RequestMapping(value="/listupAdmin", method = RequestMethod.GET)
public String listupAdmin(Model model) {
System.out.println("[AdminMemberController] listupAdmin()");
String nextPage = "admin/member/listup_admins";
List<AdminMemberVo> adminMemberVos = adminMemberService.listupAdmin();
model.addAttribute("adminMemberVos", adminMemberVos);
return nextPage;
}
관리자 목록을 조회하는 /admin/member/listupAdmin
요청을 처리한다.
model.addAttribute("adminMemberVos", adminMemberVos);
: 뷰(admin/member/listup_admins
)에 전달할 데이터를model
에 저장한다.
2) 서비스
public List<AdminMemberVo> listupAdmin() {
System.out.println("[AdminMemberService] listupAdmin()");
return adminMemberDao.selectAdmins();
}
AdminMemberService.listupAdmin()
선언.
3) DAO
public List<AdminMemberVo> selectAdmins() {
// TODO Auto-generated method stub
System.out.println("[AdminMemberDao] selectAdmins()");
String sql = "SELECT * FROM tbl_admin_member";
List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();
try {
adminMemberVos = jdbcTemplate.query(sql, new RowMapper<AdminMemberVo>() {
@Override
public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException{
AdminMemberVo adminMemberVo = new AdminMemberVo();
adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
adminMemberVo.setA_m_id(rs.getString("a_m_id"));
adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
adminMemberVo.setA_m_name(rs.getString("a_m_name"));
adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
adminMemberVo.setA_m_part(rs.getString("a_m_part"));
adminMemberVo.setA_m_position(rs.getString("a_m_position"));
adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
return adminMemberVo;
}
});
}catch (Exception e) {
e.printStackTrace();
}
return adminMemberVos;
}
String sql = "SELECT * FROM tbl_admin_member";
: 모든 관리자 정보를 조회.
4) 뷰
/BookRentalPjt/src/main/webapp/WEB-INF/views/admin/member/listup_admins.jsp
참고
...
<c:forEach var="item" items="${adminMemberVos}">
<tr>
<td>${item.a_m_id}</td>
<td>${item.a_m_name}</td>
<td>${item.a_m_gender}</td>
<td>${item.a_m_part}</td>
<td>${item.a_m_position}</td>
<td>${item.a_m_mail}</td>
<td>${item.a_m_phone}</td>
<td>
<c:choose>
<c:when test="${item.a_m_approval eq 0}">
<c:url value='/admin/member/setAdminApproval' var='approval_url'>
<c:param name='a_m_no' value='${item.a_m_no}'/>
</c:url>
<a href="${approval_url}">승인처리</a>
</c:when>
<c:when test="${item.a_m_approval eq 1}"> <c:out value="승인완료" /> </c:when>
</c:choose>
</td>
</tr>
</c:forEach>
<c:when test="${item.a_m_approval eq 1}">
: 승인받은 경우<c:when test="${item.a_m_approval eq 0}">
: 승인받지 않은 경우.
: 승인처리 링크를 누르면/admin/member/setAdminApproval?a_m_no=[해당 계정의 a_m_no]
요청 전송.
나. 일반 관리자 승인하기
“승인처리” 버튼을 누르면 /admin/member/setAdminApproval
요청을 보낸다.
1) 컨트롤러
@RequestMapping(value="/setAdminApproval", method = RequestMethod.GET)
public String setAdminApproval(@RequestParam("a_m_no") int a_m_no) {
System.out.println("[AdminMemberController] setAdminApproval()");
String nextPage = "redirect:/admin/member/listupAdmin";
adminMemberService.setAdminApproval(a_m_no);
return nextPage;
}
AdminMemberController.setAdminApproval
구현.
public String setAdminApproval(@RequestParam("a_m_no") int a_m_no) {
: 쿼리스트링에서a_m_no
값을 입력받는다.
2) 서비스
public void setAdminApproval(int a_m_no) {
// TODO Auto-generated method stub
System.out.println("[AdminMemberService] setAdminApproval()");
int result = adminMemberDao.updateAdminAccount(a_m_no);
}
3) DAO
public int updateAdminAccount(int a_m_no) {
// TODO Auto-generated method stub
System.out.println("[AdminMemberDao] updateAdminAccount()");
String sql = "UPDATE tbl_admin_member SET " + "a_m_approval = 1 " + "WHERE a_m_no = ?";
int result = -1;
try {
result = jdbcTemplate.update(sql, a_m_no);
}catch (Exception e) {
e.printStackTrace();
}
return result;
}
String sql = "UPDATE tbl_admin_member SET " + "a_m_approval = 1 " + "WHERE a_m_no = ?";
:a_m_no
로 특정 계정을 찾아서a_m_approval
를1
로 수정한다.result = jdbcTemplate.update(sql, a_m_no);
: UPDATE문은jdbcTemplate.update()
을 사용한다.
:jdbcTemplate.query()
와 달리 반환값이 없어RowMapper
는 필요 없다.
승인처리 버튼을 누르면 a_m_approval
값이 1로 변하면서 일반 관리자의 로그인이 승인된다.
이후 정상적으로 로그인할 수 있다.
'학부 강의 > 웹프로그래밍 (Spring)' 카테고리의 다른 글
[Spring] 도서 등록, 검색 (0) | 2023.12.31 |
---|---|
[Spring] Mail 보내기 (2) | 2023.12.21 |
[Spring] 데이터베이스 (0) | 2023.11.09 |
[Spring] 전자 도서관 서비스 1 (0) | 2023.11.08 |
[Spring] Service, DAO 그리고 VO (0) | 2023.11.07 |