2023. 12. 21. 20:44ㆍ학부 강의/웹프로그래밍 (Spring)
0. 출처
아직 배우고 있는 중이라 부정확한 정보가 포함되어 있을 수 있습니다!
주의하세요!
올인원 스프링 프레임워크 참고.
1. 관리자 관련 기능
- 관리자 계정 정보 수정
- 새 비밀번호 생성 및 메일 발송
- 신규 도서 등록 (+파일 업로드)
- 도서 검색 및 상세 정보 출력
- 도서 정보 수정과 삭제
2. 관리자 계정 정보 수정
가. 컨트롤러
@RequestMapping(value="/modifyAccountForm", method = RequestMethod.GET)
public String modifyAccountForm(HttpSession session) {
System.out.println("[AdminMemberController] modifyAccountForm()");
String nextPage = "admin/member/modify_account_form";
AdminMemberVo loginedAdminMemberVo = (AdminMemberVo) session.getAttribute("loginedAdminMemberVo");
if(loginedAdminMemberVo == null) nextPage = "redirect:/admin/member/loginForm";
return nextPage;
}
/admin/member/modifyAccountForm
요청을 처리하는 메서드 modifyAccountForm()
를 AdminMemberController
에 추가.
관리자가 계정수정 링크를 누르면 정보를 수정할 수 있는 페이지로 이동한다.
이때 로그인하지 않으면 로그인부터 하도록 한다.
@RequestMapping(value="/modifyAccountConfirm", method = RequestMethod.POST)
public String modifyAccountConfirm(AdminMemberVo adminMemberVo, HttpSession session) {
System.out.println("[AdminMemberController] modifyAccountConfirm()");
String nextPage = "admin/member/modify_account_ok";
int result = adminMemberService.modifyAccountConfirm(adminMemberVo);
if (result > 0) {
AdminMemberVo loginedAdminMemberVo = adminMemberService.getLoginedAdminMemberVo(adminMemberVo.getA_m_no());
session.setAttribute("loginedAdminMemberVo", loginedAdminMemberVo);
session.setMaxInactiveInterval(60 * 30);
}else {
nextPage = "admin/member/modify_account_ng";
}
return nextPage;
}
/admin/member/modifyAccountConfirm
요청을 처리하는 메서드를 AdminMemberController
에 추가.
계정수정에 성공하면 세션에 저장된 loginedAdminMemberVo
를 업데이트한다.
나. 서비스
public int modifyAccountConfirm(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberService] modifyAccountConfirm()");
return adminMemberDao.updateAdminAccount(adminMemberVo);
}
public AdminMemberVo getLoginedAdminMemberVo(int a_m_no) {
System.out.println("[AdminMemberService] getLoginedAdminMemberVo()");
return adminMemberDao.selectAdmin(a_m_no);
}
getLoginedAdminMemberVo()
: 세션 정보를 갱신하기 위해서 필요한 수정된 관리자 정보를 불러오기 위해서 필요함.
다. DAO
public int updateAdminAccount(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberDao] updateAdminAccount()");
String sql = "UPDATE tbl_admin_member SET " + "a_m_name = ?, " + "a_m_gender = ?, "
+ "a_m_part = ?, " + "a_m_position = ?, "
+ "a_m_mail = ?, " + "a_m_phone = ?, "
+ "a_m_mod_date = NOW() " + "WHERE a_m_no = ?";
int result = -1;
try {
result = jdbcTemplate.update(sql, adminMemberVo.getA_m_name(), adminMemberVo.getA_m_gender(),
adminMemberVo.getA_m_part(), adminMemberVo.getA_m_position(), adminMemberVo.getA_m_mail(),
adminMemberVo.getA_m_phone(), adminMemberVo.getA_m_no());
}catch (Exception e) {
e.printStackTrace();
}
return result;
}
public AdminMemberVo selectAdmin(int a_m_no) {
System.out.println("[AdminMemberDao] selectAdmin()");
String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_no = ?";
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;
}
}, a_m_no);
} catch (Exception e) {
e.printStackTrace();
}
return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}
계정 정보를 변경하면 데이터베이스의 데이터가 변경된다.
3. 관리자 새 비밀번호 생성
관리자 계정의 비밀번호를 잊어버렸을 때 메일로 새로운 비밀번호를 발급받는 기능을 구현해 보자.
가. JavaMailSenderImpl
- JavaMailSenderImpl : 스프링에서 메일을 보내기 위해서 사용됨.
<!-- SEND MAIL -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
pom.xml
에 dependency
를 추가한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- JavaMailSenderImpl 객체 -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.[메일 서비스 제공자].com" />
<property name="port" value="587" />
<property name="username" value="[사용할 메일]@[주소를 입력]" />
<property name="password" value="[발급받은 애플리케이션 비밀번호]" />
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.starttls.enable">true</prop>
</props>
</property>
</bean>
</beans>
BookRentalPjt/src/main/webapp/WEB-INF/spring/mail-context.xml
를 추가한다.
<property name="port" value="587" />
: 587 포트 사용.<property name="host" value="smtp.[메일 서비스 제공자].com" />
: smtp 프로토콜을 사용한다는 것을 알 수 있다.
: 메일 서비스 제공자(ex. google, naver, …)<property name="password" value="[발급받은 애플리케이션 비밀번호]" />
: 애플리케이션 비밀번호를 발급받아서 입력해야 한다.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/jdbc-context.xml
/WEB-INF/spring/security-context.xml
/WEB-INF/spring/mail-context.xml
</param-value>
</context-param>
web.xml
수정.
나. 새 비밀번호 생성
1) 컨트롤러
@RequestMapping(value="/findPasswordForm", method = RequestMethod.GET)
public String findPasswordForm() {
System.out.println("[AdminMemberController] findPasswordForm()");
String nextPage = "admin/member/find_password_form";
return nextPage;
}
비밀번호 찾기 페이지 이동.
<%@ 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/find_password_form.css' />" rel="stylesheet" type="text/css">
<jsp:include page="../include/find_password_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>FIND PASSWORD FORM</h3>
<p>(We will send you a new password.)</p>
</div>
<div class="find_password_form">
<form action="<c:url value='/admin/member/findPasswordConfirm' />" name="find_password_form" method="post">
<input type="text" name="a_m_id" placeholder="INPUT ADMIN ID."> <br>
<input type="text" name="a_m_name" placeholder="INPUT ADMIN NAME."> <br>
<input type="text" name="a_m_mail" placeholder="INPUT ADMIN MAIL."> <br>
<input type="button" value="find password" onclick="findPassword();">
<input type="reset" value="reset">
</form>
</div>
<div class="create_account_login">
<a href="<c:url value='/admin/member/createAccountForm' />">create account</a>
<a href="<c:url value='/admin/member/loginForm' />">login</a>
</div>
</div>
</section>
<jsp:include page="../../include/footer.jsp" />
</body>
</html>
admin/member/find_password_form.jsp
(출판사에서 제공하는 실습 코드에서 jsp 파일을 가져온다.)
@RequestMapping(value="/findPasswordConfirm", method = RequestMethod.POST)
public String findPasswordConfirm(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberController] findPasswordConfirm()");
String nextPage = "admin/member/find_password_ok";
int result = adminMemberService.findPasswordConfirm(adminMemberVo);
if(result <= 0)
nextPage = "admin/member/find_password_ng";
return nextPage;
}
findPasswordConfirm()
추가.
2) 서비스
public int findPasswordConfirm(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberService] findPasswordConfirm()");
AdminMemberVo selectedAdminMemberVo = adminMemberDao.selectAdmin(adminMemberVo.getA_m_id(), adminMemberVo.getA_m_name(), adminMemberVo.getA_m_mail());
int result = 0;
if(selectedAdminMemberVo != null) {
String newPassword = createNewPassword();
result = adminMemberDao.updatePassword(adminMemberVo.getA_m_id(), newPassword);
if(result > 0) sendNewPasswordByMail(adminMemberVo.getA_m_mail(), newPassword);
}
return result;
}
AdminMemberVo selectedAdminMemberVo = adminMemberDao.selectAdmin(adminMemberVo.getA_m_id(), adminMemberVo.getA_m_name(), adminMemberVo.getA_m_mail());
: 전달받은 id, name, mail을 바탕으로 계정이 존재하는지 우선 확인한다.String newPassword = createNewPassword();
: 새로운 비밀번호를 생성한다.result = adminMemberDao.updatePassword(adminMemberVo.getA_m_id(), newPassword);
: 비밀번호를 업데이트한다.if(result > 0) sendNewPasswordByMail(adminMemberVo.getA_m_mail(), newPassword);
: 정상적으로 비밀번호를 업데이트했다면 변경된 비밀번호를 메일로 전송한다.
@Autowired
JavaMailSenderImpl javaMailSenderImpl;
private String createNewPassword() {
System.out.println("[AdminMemberService] createNewPassword()");
char[] chars = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
};
StringBuffer stringBuffer = new StringBuffer();
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(new Date().getTime());
int index = 0, length = chars.length;
for(int i =0; i < 8; i++) {
index = secureRandom.nextInt(length);
if(index % 2 == 0)
stringBuffer.append(String.valueOf(chars[index]).toUpperCase());
else
stringBuffer.append(String.valueOf(chars[index]).toLowerCase());
}
System.out.println("[AdminMemberService] NEW PASSWORD: " + stringBuffer.toString());
return stringBuffer.toString();
}
...
SecureRandom
는 Random
보다 더 복잡한 난수를 생성한다.
String
이 아닌 굳이StringBuffer
을 사용한 이유- 효율성 : 자바에서
String
객체는 불변(immutable)이다. 즉, 문자열에 변경이 생길 때마다 새로운String
객체가 생성되고 기존 객체는 가비지 컬렉터에 의해 제거된다. 이는 메모리 사용량이 많고 성능 저하를 일으킬 수 있다. 반면,StringBuffer
는 가변(mutable)이어서 기존 객체에 문자열을 추가하거나 변경할 수 있다. 따라서 반복적인 문자열 조작이 필요한 경우StringBuffer
를 사용하는 것이 더 효율적이다. (출처 : ChatGPT) - 스레드 안전성 :
StringBuffer
는 스레드 안전(thread-safe)하다는 특징을 가지고 있다. 즉, 멀티스레드 환경에서 동시에 접근해도 데이터 무결성이 유지된다. 각 메서드가synchronized
키워드로 동기화되어 있기 때문이다. 이는 여러 스레드가 동시에 해당 객체를 수정할 때 발생할 수 있는 문제를 방지한다. (출처 : ChatGPT)
- 효율성 : 자바에서
secureRandom.setSeed(new Date().getTime());
: 난수를 생성할 때 시간을 참고한다.
: 하지만! 높은 보안성을 위해서 따로 시드를 설정하지 않는 것을 추천한다.
: 특별한 경우가 아니라면SecureRandom
의 자동 시드 생성 메커니즘을 그대로 사용하는 것이 좋다.
:SecureRandom
클래스는 기본적으로 시스템에서 제공하는 엔트로피 소스(예: 시스템 시간, 하드웨어 랜덤성 소스 등)를 사용하여 난수 생성의 기본 시드를 자동으로 생성한다. 이 방식은 매우 높은 엔트로피를 제공하므로, 생성된 난수의 예측이 어렵게 만들어 보안을 강화한다.index = secureRandom.nextInt(length);
: 무작위로 인덱스를 선택합니다.if(index % 2 == 0) stringBuffer.append(String.valueOf(chars[index]).toUpperCase()); else stringBuffer.append(String.valueOf(chars[index]).toLowerCase());
: 대소문자를 섞어서 비밀번호를 생성한다.
private void sendNewPasswordByMail(String toMailAddr, String newPassword) {
System.out.println("[AdminMemberService] sendNewPasswordByMail()");
final MimeMessagePreparator mimeMessagePreparator = new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception{
final MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
mimeMessageHelper.setTo(toMailAddr);
mimeMessageHelper.setSubject("[한국도서관] 새 비밀번호 안내입니다.");
mimeMessageHelper.setText("새 비밀번호 : " + newPassword, true);
}
};
javaMailSenderImpl.send(mimeMessagePreparator);
}
변경된 새 비밀번호를 전달받은 이메일 주소로 보낸다.
MimeMessagePreparator.prepare()
동작 중에 MimeMessageHelper
로 구체적인 MimeMessage
를 작성한다.
JavaMailSenderImpl
이 준비된 MimeMessagePreparator
를 보낸다.
아래는 보다 다양한 MimeMessageHelper
기능이다.
3) DAO
public AdminMemberVo selectAdmin(String a_m_id, String a_m_name, String a_m_mail) {
System.out.println("[AdminMemberDao] selectAdmin()");
String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_id = ? AND a_m_name = ? AND a_m_mail = ?";
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;
}
}, a_m_id, a_m_name, a_m_mail);
} catch (Exception e) {
e.printStackTrace();
}
return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}
관리자의 비밀번호를 재설정 요청에 대하여 올바른 접근인지 확인한다.
public int updatePassword(String a_m_id, String newPassword) {
System.out.println("[AdminMemberDao] updatePassword()");
String sql = "UPDATE tbl_admin_member SET " + "a_m_pw = ?, " + "a_m_date = NOW() "
+ "WHERE a_m_id = ?";
int result = -1;
try {
result = jdbcTemplate.update(sql, passwordEncoder.encode(newPassword), a_m_id);
}catch (Exception e) {
e.printStackTrace();
}
return result;
}
비밀번호를 업데이트한다.
당연히 암호화하여 저장한다.
'학부 강의 > 웹프로그래밍 (Spring)' 카테고리의 다른 글
[Spring] 도서 수정, 삭제 (0) | 2024.01.01 |
---|---|
[Spring] 도서 등록, 검색 (0) | 2023.12.31 |
[Spring] 관리자 로그인 (2) | 2023.12.20 |
[Spring] 데이터베이스 (0) | 2023.11.09 |
[Spring] 전자 도서관 서비스 1 (0) | 2023.11.08 |