[Framework] TIL 055 - 23.10.05

유진·2023년 10월 5일
0

07_Framework

  • 예외처리

error.jsp

<%@ 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 lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error</title>
    <style>
        #error-container{
            width: 800px;
            height: 300px;
            text-align: center;
            
            position: absolute;
            top : 0; bottom: 0; left: 0; right: 0;
            margin: auto;
        }
        
        #error-container > h1{ margin-bottom: 50px; }

        .error-cnotent-title{
            text-align: left;
            font-weight: bold;
        }
        
        #btn-area{ text-align: center;  }

    </style>
</head>
<body>

    <div id="error-container">
        <h1>서비스 이용 중 문제가 발생했습니다</h1>
        
        <span class="error-content-title"> 발생한 예외 : ${e}</span>
        <p>
            자세한 문제 원인은 이클립스 콘솔을 확인해주세요.
        </p>
        
        <div id="btn-area">
            <a href="/">메인 페이지</a>
            <%-- <a href="${header.referer}">이전 페이지</a> --%>
            
            <button onclick="history.back()">이전 페이지</button>
            
            <!-- referer : 페이지 방문 흔적 -->
        </div>
    </div>

    
    
</body>
</html>

member-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="memberMapper">
	<!-- namespace : 해당 공간의 이름 지정 -->

	<!-- mapper 파일 생성 시 아래 태그 반드시 삭제 -->
	<!--   <cache-ref namespace=""/>   -->
	
	<!-- 작성예시 -->
	<!-- <select id=""></select>  -->
	<!-- <insert id=""></insert>  -->
	
	<!-- 
		resultMap  
		- SELECT 조회 결과(ResultSet) 컬럼명과
		  컬럼 값을 옮겨 담을 DTO의 필드명이 같지 않을 때
		  이를 매핑 시켜 SELECT시 자동으로 담기게하는 역할
	 
	 
	 	-속성
	 	type : 연결할 DTO (패키지명 + 클래스명 또는 별칭)
	 	id : 만들어진 resultMap을 지칭할 식별명(이름)
	 	
	 	<id> 태그 : PK 역할 컬럼 - 필드 매핑
	 	<result> 태그 : <id>제외 나머지
	 -->
	 
	<resultMap type="Member" id="member_rm">
	
	<!-- property가 java, column이 db라고 생각하면 됨. -->
	
		<!-- DB의 기본 키(복합키면 여러 개 작성) -->
		<id property="memberNo" column="MEMBER_NO" />

		<!-- DB의 일반 컬럼들 -->
		<result property="memberEmail" column="MEMBER_EMAIL" />
		<result property="memberPw" column="MEMBER_PW" />
		<result property="memberNickname" column="MEMBER_NICKNAME" />
		<result property="memberTel" column="MEMBER_TEL" />
		
		<result property="memberAddress" column="MEMBER_ADDR" />
		
		<result property="profileImage" column="PROFILE_IMG" />
		<result property="enrollDate" column="ENROLL_DATE" />
		<result property="memberDeleteFlag" column="MEMBER_DEL_FL" />
		<result property="authority" column="AUTHORITY" />
	</resultMap>
	
	<!--  
  		SQL 관련 태그의 속성
  		
  		- parameterType : 전달 받은 값의 자료형
  						기본 : 패키지명 + 클래스명
  						별칭 : Mybatis 별칭 또는 사용자 지정 별칭
  						
		- parameterMap : (사용 안함)
		
		- resultType : select 결과를 담아서 반환할 자료형
					단, DTO를 작성할 경우 필드명 = 컬럼명 인 경우만 가능
										memberNo   MEMBER_NO
		
		- resultMap : select 결과의 컬럼명과 
					 결과를 저장할 DTO 필드명이 다를 경우
					 이를 알맞게 매핑(연결)시켜주는 <resultMap> id 작성
  	-->
  	
  	
  	<!-- 
  		** 마이바티스에서 전달 받은 값을 SQL에 작성하는 방법 **
  		
  		#{변수명|필드명} : PreparedStatement -> placeholder 대체하여 사용
  						 : SQL에 값 대입 시 양쪽에 '' 붙여서 대입
  		
  		${변수명|필드명} : Statement -> '숫자' 쓸 때 많이 사용
  						 : SQL에 값 대입 시 양쪽에 아무것도 붙이지 않음
 						 
		사용 예시)
		test1 = "user01"
		test2 = MEMBER_EMAIL
  	
  		- MEMBER_EMAIL이 'user01'인 회원 조회
  		SELECT * FROM MEMBER WHERE ${test2} = #{test1}
  								 MEMBER_EMAIL = 'user01'
  	 -->
	
	<!-- 
		<select></select>
		<insert></insert>
		<update></update>
		<delete></delete>
		사용 가능한 속성도 조금씩 다름
	 -->
	<select id="login" parameterType="Member" resultMap="member_rm">
  		SELECT MEMBER_NO, MEMBER_EMAIL, MEMBER_NICKNAME, MEMBER_PW,
			MEMBER_TEL, MEMBER_ADDR, PROFILE_IMG, AUTHORITY,
			TO_CHAR(ENROLL_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') AS ENROLL_DATE 
		FROM "MEMBER"
		WHERE MEMBER_DEL_FL = 'N'
		AND MEMBER_EMAIL = #{memberEmail}; 
		<!-- AND MEMBER_PW = #{memberPw} -->
  	</select>
	
</mapper>
  • 위 경우처럼 sql 구문에 ;와 같은 오류가 생길 경우,

1) ExceptionHandler

MemberController.java

package edu.kh.project.member.controller;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.member.model.dto.Member;
import edu.kh.project.member.model.service.MemberService;

@Controller // 요청/응답 클래스 + bean 등록(Spring 관리하는 객체)
@RequestMapping("/member") // 공통된 주소 앞부분 작성, member로 시작하는 요청은 해당 컨트롤러에서 처리
@SessionAttributes({"loginMember"}) // Model의 이름(key)를 적으면 session으로 추가
									// 올리고 싶은게 여러 개일 수 있기 때문에 나열하고 싶으면 {} 사용하여 적기
public class MemberController {
	
	// 등록된 Bean 중에서 필드와 타입이 일치하는 Bean 주입
	// -> MemberService를 구현한 MemberServiceImpl의 Bean 주입
	@Autowired
	private MemberService service;
	
	

	// 로그인 : /member/login
	// 로그아웃 : /member/logout
	
	// /member/login, post 처리방식
	
	
	// @RequestMapping : 요청 주소에 맞는 클래스/메서드 연결
	// @RequestMapping("요청주소") : -> GET / POST 구분 X , 주소만 맞으면 연결하지만 GET요청시 사용
	// @RequestMapping(value="/login", method=RequestMethod.GET/POST) -> GET/POST 방식을 구분
	
    // @RequestMapping(value="/login", method=RequestMethod.POST)
	public String login(HttpServletRequest req) {
		
		// 파라미터 전달 방법 1 : HttpServletRequest를 이용하는 방법
		// -> Controller 메서드에 매개변수로 HttpServletRequest를 작성
		
		// 매개변수에 적으면 사용 가능한 이유
		// Spring 제공하는 Argument Resolver(매개변수해결사)가 자동으로
		// 대입해주고 해결해줌.
		
		String inputEmail = req.getParameter("inputEmail");
		String inputPw = req.getParameter("inputPw");
		
		System.out.println("inputEmail : " + inputEmail);
		System.out.println("inputPw : " + inputPw);
		
		
		// ******************* redirect 방법! *******************
		// "redirect:요청주소"
		
		return "redirect:/";
	}
	
	
	// @PostMapping : @RequestMapping 자식으로
	//				Post 방식 요청을 연결하는 어노테이션
	//@PostMapping("/login")
	public String login(/*@RequestParam("inputEmail")*/ String inputEmail,
						/*@RequestParam("inputPw")*/ String inputPw/*,
						@RequestParam(value="saveId", required="fasle", defaultValue="1")*/) {
		
		// 파라미터 전달 방법 2 : @RequestParam 어노테이션 이용(+생략방법)
		
		// @RequestParam 어노테이션
		// - request 객체를 이용한 파라미터 전달 어노테이션
		// - 매개변수 앞에 해당 어노테이션 작성하면, 매개변수에 값이 주입됨.
		
		// ** 파라미터의 name 속성값과
		//	매개변수명이 같으면 @RequestParam 생략 가능! **
		
		
		// @RequestParam(value="name", required="fasle", defaultValue="1")
		// [속성]
		// value : 전달 받은 input 태그의 name 속성값
		
		// required : 입력된 name 속성값 파라미터 필수 여부 지정(기본값 true)
		// -> required = true인 파라미터가 존재하지 않는다면 400 Bad Request 에러 발생
		// -> required = true인 파라미터가 null인 경우에도 400 Bad Request

		// defaultValue : 파라미터 중 일치하는 name 속성 값이 없을 경우에 대입할 값 지정.
		// -> required = false인 경우 사용
		
		
		System.out.println("inputEmail : " + inputEmail);
		System.out.println("inputPw : " + inputPw);

		
		return "redirect:/";
	}
	
	
	//@PostMapping("/login")
	public String login(/*@ModelAttribute*/ Member inputMember) {
		
		// 파라미터 전달 방법 3 : @ModelAttribute 이용한 방법
		
		// - DTO(또는 VO)와 같이 사용하는 어노테이션
		
		// - 전달 받은 파라미터의 name 속성 값이
		//   같이 사용되는 DTO의 필드명과 같다면
		//   자동으로 setter를 호출해서 필드에 값을 세팅
		
		System.out.println(inputMember);
		
		// ** @ModelAttribute 사용 시 주의 사항 **
		// - DTO에 기본 생성자가 필수로 존재해야 한다
		// - DTO에 setter가 필수로 존재해야 한다
		
		// ** ModelAttribute 어노테이션은 생략이 가능하다 ! ** 
		
		// ** '@ModelAttribute'를 이용해 값이 필드에 세팅된 객체를
		//	'커맨드 객체' 라고 부른다.
		
		
		// Member [ memberEmail = user123, memberPw = pass123.. ]
		
		
		return "redirect:/";
	}
	
	
	/* 찐 로그인 메서드 */
	
	// Session : '서버' -> 보안에 더 유리
	// Cookie : '클라이언트'(브라우저) -> 속도는 조금 더 빠름
	
	@PostMapping("/login")
	public String login(Member inputMember, Model model, // 파라미터 추가할때마다 , 적어주기!
						@RequestHeader("referer") String referer,
						RedirectAttributes ra,
						@RequestParam(value="saveId", required=false) String saveId,
						HttpServletResponse resp
						) {
		
		// @RequestHeader(value="referer") String referer
		// -> 요청 HTTP header에서 "referer" (이전 주소) 값을 얻어와
		//  매개 변수 String referer에 저장
		
		
		// Model : 데이터 전달용 객체
		// -> 데이터를 K : V 형식으로 담아 전달
		// -> 기본적으로 request scope
		// -> @SessionAttributes 어노테이션과 함께 사용 시 session scope
		
		// @RequestParam(value="saveId", required=false) String saveId
		// -> name 속성값이 saveId인 파라미터를 전달받아서 저장
		// required 미작성 시 기본 값 true
		// required = false : 필수 아님(null 허용) 
		
		

		// 로그인 서비스 호출
		Member loginMember = service.login(inputMember);
		
		// DB 조회 결과 확인
		//System.out.println(loginMember);
		
		// 로그인 결과에 따라 리다이렉트 경로를 다르게 지정
		String path = "redirect:";
		
		if(loginMember != null) {  // 로그인 성공시
			path += "/";  // 메인페이지로 리다이렉트
			
			// session loginMember 추가
			
			// Session에 로그인한 회원 정보 추가
			// Servlet : HttpSession.setAttribute(key, value)
			// Spring  : Model + @SessionAttributes
			
			// 1) model에 로그인한 회원 정보 추가
			model.addAttribute("loginMember", loginMember);
			// -> 현재는 request scope
			
			// 2) 클래스 위에 @SessionAttributes 추가
			// -> 이제 session scope
			
			// --------------------------------------
			
			// 아이디 저장 (Cookie)
			
			/* Cookie란?
			 * - 클라이언트 측(브라우저)에서 관리하는 파일
			 * 
			 * - 쿠키파일에 등록된 주소 요청 시 마다
			 * 	 자동으로 요청에 첨부되어 서버로 전달됨.
			 * 
			 * - 서버로 전달된 쿠키에
			 * 	 값 추가, 수정, 삭제 등을 진행한 후
			 * 	 다시 클라이언트에게 반환
			 * 
			 */
			
			// 쿠키 생성(해당 쿠키에 담을 데이터를 k:v 로 지정)
			Cookie cookie = new Cookie("saveId", loginMember.getMemberEmail());
			
			if(saveId != null) { // 체크가 되었을 때
				
				// 한 달(30일) 동안 유지되는 쿠키 생성
				cookie.setMaxAge(60*60*24*30); // 초단위 지정
				
				
			} else { // 체크가 안되었을 때
				
				// 0초 동안 유지되는 쿠키 생성
				// -> 기존에 쿠키가 지정되어 있었다면 해당 쿠키를 삭제
				cookie.setMaxAge(0);
				
			}
			
			// 클라이언트가 어떤 요청을 할 때 쿠키가 첨부될지 경로(주소)를 지정
			cookie.setPath("/"); // localhost:/ 이하 모든 주소
								// ex) / , /member/login , /member/logout 등
								// 모든 요청에 쿠키를 첨부
			
			// 응답 객체(HttpServletResponse) 를 이용해서
			// 만들어진 쿠키를 클라이언트에게 전달
			resp.addCookie(cookie);
			
			
		} else {  // 로그인 실패
			path += referer;
			
			// message 추가 (아이디 또는 비밀번호 불일치)
			
			/* redirect(재요청) 시
			 * 기존 요청(request)이 사라지고
			 * 새로운 요청(request)을 만들게 되어
			 * redirect된 페이지에서는 이전 요청이 유지 되지 않는다!
			 * -> 유지 하고 싶으면 어쩔 수 없이 session scope를 이용
			 * 
			 * RedirectAttributes를 스프링에서 제공
			 * - 리다이렉트 시 데이터를 request scope로 전달할 수 있게하는 객체
			 * 
			 * 응답 전 : request scope
			 * 
			 * 응답 중 : session scope로 잠시 이동
			 * 
			 * 응답 후 : request scope로 복귀
			 * 
			 */
			
			// addFlashAttribute : 잠시 session 에 추가
			ra.addFlashAttribute("message", "아이디 또는 비밀번호 불일치");
		}
		
		return path;
	}
	
	
	@GetMapping("/logout")
	public String logout(SessionStatus status/*HttpSession session*/) { // 두 방법 중 하나 사용하면 됨!
		
		// SessionStatus : 세션 상태를 관리하는 객체
		
		//session.invalidate(); // 세션 무효화
		status.setComplete();
		
		return "redirect:/";
	}
	
	
	
	/*
	 *  스프링 예외 처리 방법(3종류, 중복 사용 가능)
	 * 
	 *  1 순위 : 메서드 단위로 처리
	 * 		    -> try - catch / throws
	 * 
	 * 
	 *  2 순위 : 클래스 단위로 처리
	 * 		    -> @ExceptionHandler
	 * 
	 * 
	 *  3 순위 : 프로그램 단위(전역) 처리
	 *          -> @ControllerAdvice
	 * 
	 */
	
	// 현재 클래스에서 발생하는 모든 예외를 모아서 처리
	//@ExceptionHandler(Exception.class /* 예외처리 하고 싶은 것 넣어주기 */)
	public String exceptionHandler(Exception e, Model model) {
		
		// Exception e : 예외 정보를 담고있는 객체
		// Model model : 데이터 전달용 객체 (request scope 기본)
		
		e.printStackTrace(); // 예외 내용/발생 메서드 확인
		
		// request 범위? 현재페이지, 위임받은 페이지
		model.addAttribute("e", e); // 예외 발생 시 forward되는 페이지로 e를 전달함. (request scope도 가능하다)
		
		
		// 누구에 의해서?
		// View Resolver의 prefix, suffix를 붙여 JSP 경로를 만든것
		// return "/WEB-INF/views/common/error.jsp";
		return "common/error";
	}
	
}

2) ControllerAdvice

ExceptionController.java

package edu.kh.project.common;

import java.io.IOException;
import java.sql.SQLException;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

// 예외 처리용 컨트롤러 (프로젝트 전역)
@ControllerAdvice
public class ExceptionController {

	@ExceptionHandler(Exception.class)
	public String exceptionHandler(Exception e, Model model) {
		
		// Exception e : 예외 정보를 담고있는 객체
		// Model model : 데이터 전달용 객체 (request scope 기본)
		
		e.printStackTrace(); // 예외 내용/발생 메서드 확인
		
		// request 범위? 현재페이지, 위임받은 페이지
		model.addAttribute("e", e); // 예외 발생 시 forward되는 페이지로 e를 전달함. (request scope도 가능하다)
		
		
		// 누구에 의해서?
		// View Resolver의 prefix, suffix를 붙여 JSP 경로를 만든것
		// return "/WEB-INF/views/common/error.jsp";
		return "common/error";
	}
	
	
//	@ExceptionHandler(SQLException.class)
//	public String exceptionHandler2(Exception e, Model model) {
//		
//		// Exception e : 예외 정보를 담고있는 객체
//		// Model model : 데이터 전달용 객체 (request scope 기본)
//		
//		e.printStackTrace(); // 예외 내용/발생 메서드 확인
//		
//		// request 범위? 현재페이지, 위임받은 페이지
//		model.addAttribute("e", e); // 예외 발생 시 forward되는 페이지로 e를 전달함. (request scope도 가능하다)
//		
//		
//		// 누구에 의해서?
//		// View Resolver의 prefix, suffix를 붙여 JSP 경로를 만든것
//		// return "/WEB-INF/views/common/error.jsp";
//		return "common/error";
//	}
//	
//	
//	@ExceptionHandler(IOException.class)
//	public String exceptionHandler3(Exception e, Model model) {
//		
//		// Exception e : 예외 정보를 담고있는 객체
//		// Model model : 데이터 전달용 객체 (request scope 기본)
//		
//		e.printStackTrace(); // 예외 내용/발생 메서드 확인
//		
//		// request 범위? 현재페이지, 위임받은 페이지
//		model.addAttribute("e", e); // 예외 발생 시 forward되는 페이지로 e를 전달함. (request scope도 가능하다)
//		
//		
//		// 누구에 의해서?
//		// View Resolver의 prefix, suffix를 붙여 JSP 경로를 만든것
//		// return "/WEB-INF/views/common/error.jsp";
//		return "common/error";
//	}
	
}


비밀번호 잘못 입력 시,
해당 페이지로 이동


  • 암호화


암호화가 되어있지 않은 상태(평문) = 개인정보보호법에 위반! -> 암호화 필요


암호화가 되어있는 상태 (실제 비밀번호: asd123 / DB PW: 'Bcrypt 암호화' 상태)
** Bcrypt -> DB에서 비교할 수 X, service java단에서 비밀번호 비교할 것임.

cf ) 예전: sha 방식 암호화 사용 (암호화시 비밀번호가 동일하게 됨 -> 해킹위험이 큼!)

  • ex ) A회원 / 비밀번호 1234 -> 암호화 abcd
    B회원 / 비밀번호 1234 -> 암호화 abcd

이러한 문제점을 해결할 수 있는, 'Bcrypt 암호화' 방식 등장.
Bcrypt = Salt 방식을 거침 (매번 다른 암호화 보여줌, 로그인 시도 시 암호화되는 방식이 계속 변경됨.)

  • ex ) 비밀번호 1234 -> @#G#Ag45
    비밀번호 1234 -> 45dg@Fsg3
  • MVN REPOSITORY 필요!


-> 3가지 필요

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>edu.kh</groupId>
	<artifactId>project</artifactId>
	<name>boardProject</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	
	<!-- properties : 메이븐이 적용된 프로젝트에서 공통적으로 사용할 값을 작성하는 태그 -->
	<properties>
		<java-version>11</java-version>
		<org.springframework-version>5.3.14</org.springframework-version>
		<org.aspectj-version>1.9.9.1</org.aspectj-version>
		<org.slf4j-version>1.7.25</org.slf4j-version>
	</properties>
	
	<!-- dependencies : Maven 프로젝트는 외부 저장소와 의존 관계를 맺고 있어 프로젝트에 필요한 파일(라이브러리)을
		사용자가 직접 다운 받을 필요 없이, 해당 태그 내에 지정된 형식으로 작성하면 네트워크를 통해 외부 저장소에서 자동으로 얻어와 세팅함
	 -->
	<dependencies>
	
		<!-- lombok 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
		    <groupId>org.projectlombok</groupId>
		    <artifactId>lombok</artifactId>
		    <version>1.18.24</version>
		    <scope>provided</scope>
		</dependency>
		
		<!-- 오라클 JDBC 드라이버 -->
		<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc11 -->
		<dependency>
		    <groupId>com.oracle.database.jdbc</groupId>
		    <artifactId>ojdbc11</artifactId>
		    <version>21.5.0.0</version>
		</dependency>
		
		<!-- 스프링에서 JDBC를 사용할 수 있게하는 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-jdbc</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		
		<!-- Mybatis 영속성 프레임워크 -->
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis</artifactId>
		    <version>3.5.9</version>
		</dependency>
		
		<!-- Spring - Mybatis 연결 모듈, 연결 역할을 하는 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis-spring</artifactId>
		    <version>2.0.6</version>
		</dependency>
		
		<!-- 커넥션 풀 기능을 사용하기 위한 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-dbcp2</artifactId>
		    <version>2.9.0</version>
		</dependency>
		
		
		<!-- Spring-security 모듈 추가 -->
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-core</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-web</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-config</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		
		
		
		
		<!-- Spring 모듈 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
				
		<!-- AspectJ : AOP 기능을 사용하기 위한 언어 문법 -->
		<!-- aspectjrt : AspectJ 런타임 프로그램 -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	
		
		<!-- AspectJ Weaver : aspect의 정보를 바탕으로 aspect를 구성한 코드를 생성하는데 필요한 유틸리티 프로그램 -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>
		
		<!-- Log4j (Logging) -->
		<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
		<dependency>
		    <groupId>org.apache.logging.log4j</groupId>
		    <artifactId>log4j-core</artifactId>
		    <version>2.17.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
				
		<!-- Servlet -->
		<!-- Servlet 버전을 4.0으로 변경 -->
		<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>javax.servlet-api</artifactId>
		    <version>4.0.1</version>
		    <scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		
		
		<!-- 단위 테스트 도구 (JUnit) -->
		<!-- https://mvnrepository.com/artifact/junit/junit -->
		<dependency>
		    <groupId>junit</groupId>
		    <artifactId>junit</artifactId>
		    <version>4.13.2</version>
		    <scope>test</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-test</artifactId>
		    <version>${org.springframework-version}</version>
		    <scope>test</scope>
		</dependency>    
	</dependencies>
	
	<!-- build : 프로젝트 빌드 시 사용되는 플러그인 추가 및 버전 정보 설정 -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            
            <!-- 컴파일러 플러그인은 프로젝트의 소스(자바코드)를 컴파일하는 데 사용
			jdk 1.6 이상 사용 시 3.0 이상 버전을 사용, source, taget에는 사용하는 jdk 버전을 작성 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            
            <!-- Could not initialize class org.apache.maven.plugin.war.util.WebappStructureSerializer -->
			<!-- 메이븐 구성 문제로 인한 pom.xml 오류 발생 시 해결 -->
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.2</version>
			</plugin>
        </plugins>
    </build>
</project>

  • Bean 파일 생성



    FINISH

spring-security.xml

<?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:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-5.7.xsd">

	<!-- xml을 이용한 bcrypt 암호화 객체를 bean으로 등록 -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://JAVA.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_4_0.xsd">

	<!-- web.xml(배포 서술자)
		서버가 켜질때 (배포가 시작될 때) 가장 먼저 읽어들이는 설정 파일
	-->

	<!-- root-context.xml
		서버가 켜질 때 web.xml이 가장 먼저 읽어들이는 설정 파일
		프로젝트 전반적으로 사용될 설정, 객체(Bean)를 생성하는 용도의 파일
		
		* classpath : JVM이 프로그램 실행 시 클래스 및 설정파일을 찾는 기준이 되는 경로
		Spring MVC Project는 src/main/java , src/main/resources 두 폴더가 포함이 된다
	 -->


	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:spring/root-context.xml
			classpath:spring/spring-security.xml
		</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	
	<!-- 
	
		DispatcherServlet(Spring 제공) 객체 생성 시
		servlet-context.xml 파일을 이용해서 만든다
	 -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	
	
	<!-- 한글 깨짐 방지를 위한 Filter 추가 -->
	<!-- 별도의 filter 클래스를 만들지 않고 스프링에서 제공하는 filter를 사용 -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

  • DB에서 할 수 없기 때문에 JAVA단에서 처리!

입력받은 비밀번호(평문) --비교-- DB에서 조회한 비밀번호(암호화)

MemberServiceImpl.java

package edu.kh.project.member.model.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import edu.kh.project.member.model.dao.MemberDAO;
import edu.kh.project.member.model.dto.Member;

@Service // 비즈니스 로직(데이터 가공, DAO 호출, 트랜잭션 제어)처리하는 클래스 명시
		// + Bean으로 등록하는 어노테이션
public class MemberServiceImpl implements MemberService{
	
	// @Autowired : 작성된 필드와
	// Bean으로 등록된 객체 중 타입이 일치하는 Bean을
	// 해당 필드에 자동 주입(Injection) 하는 어노테이션.
	// == DI(Dependency Injection, 의존성 주입)
	// -> 객체를 직접 만들지 않고, Spring이 만든걸 주입함(Spring에 의존)
	
	@Autowired
	private MemberDAO dao;
	
	@Autowired // bean으로 등록된 객체 중 타입이 일치하는 객체를 DI(의존성 주입)
	private BCryptPasswordEncoder bcrypt;
	
	// 암호화가 필요한 곳? 로그인, 회원가입
	
	@Override
	public Member login(Member inputMember) {
		
		// 암호화가 다 다른지 test 용도
//		System.out.println("암호화 확인 1:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 2:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 3:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 4:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 5:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
		
		
		// dao 메서드 호출
		Member loginMember = dao.login(inputMember);
		
		// 입력받은 비밀번호(평문) --비교-- DB에서 조회한 비밀번호(암호화)
		
		if(loginMember != null) { // 아이디가 일치하는 회원이 조회된 경우
			
			// 입력한 pw, 암호화된 pw 같은지 확인
			
			// 같을 경우            // 로그인 시 입력             // DB에서 조회한 것
			if(bcrypt.matches(inputMember.getMemberPw(), loginMember.getMemberPw())) {
				
				// 비밀번호를 유지하지 않기 위해서 로그인 정보에서 제거
				loginMember.setMemberPw(null);
				
			} else { // 다를 경우(비밀번호 잘못쳤을 경우)
				loginMember = null;
			}
			
		} 
		
		return loginMember;
	}
	
	
}


올바른 비밀번호 입력 후 로그인 시,

잘못된 비밀번호 입력 후 로그인 시,
팝업창 출력 후 원래 로그인 화면으로 돌아감


  • 비동기통신
  • Ajax = 웹페이지에서 가장 많이 사용하는 기술







-> jquery 사용시 Ajax 사용이 간단해짐 -> 요즘은 jauery 사용하지 않음
-> 요즘은 최신 방법인 'fetch API' 사용!

main.jsp

<%@ 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">
	<title>프로젝트</title>
	
	<!-- fontaswesom 아이콘 사용할 수 있는 스크립트 연결-->
	<script src="https://kit.fontawesome.com/f821b57119.js" crossorigin="anonymous"></script>
</head>
<body>
	<main>
        
        
        <%-- header.jsp 추가 --%>
        
        <%--
        	<jsp:include page="jsp파일경로" />
        	
        	- jsp 파일 경로는 'webapp 폴더 기준'으로 작성
        	- JSP 액션 태그(jsp에 기본 내장됨)
        	- 다른 jsp 파일의 코드를 현재 위치에 추가
        
         --%>
         
        <jsp:include page="/WEB-INF/views/common/header.jsp" />
		

        <section class="content">
            <section class="content-1">
            	<h3>로그인된 회원 정보</h3>
             	${sessionScope.loginMember}
             	
             	
             	<h3>닉네임이 일치하는 회원의 전화번호 조회</h3>
            	
            	<input type="text" id="inputNickname">
            	<button id="btn1">조회</button>
            	<h4 id="result1"></h4>
            	
            	<hr>
            
            </section>
            

            <!-- 아이디/비밀번호/로그인버튼 영역 -->
            <section class="content-2">
            
            	<c:choose>
            		<%-- 로그인이 안되었을때 --%>
            		<%-- EL empty : 비어있거나 null이면 true --%>
            		<c:when test="${empty sessionScope.loginMember}">
            		
	            		<form action="/member/login" method="post" name="login-form" id="loginFrm">
	
		                    <fieldset class="id-pw-area">
		                        <section>
		                            <input type="text" name="memberEmail" placeholder="이메일"
		                            	autocomplete="off"
		                            	value="${cookie.saveId.value}"
		                            	>
		                            <input type="password" name="memberPw" placeholder="비밀번호">
		                        </section>
		
		                        <section>
		                            <button>로그인</button>
		                        </section>
		                    </fieldset>
		
		                    <label>
		                    
		                    	<c:if test="${not empty cookie.saveId.value}">
		                    		<%-- 쿠키에 저장된 이메일이 있으면 변수 선언 : save --%>
		                    		
		                    		<c:set var="save" value="checked"/>
		                    	</c:if>
		                    	
		                        <input type="checkbox" name="saveId" ${save}> 아이디 저장
		                        
		                    </label>
		
		                    <!-- 회원가입/ Id/pw 찾기 영역 -->
		                    <section class="signup-find-area">
		                        <a href="#">회원가입</a>
		                        <span>|</span>
		                        <a href="#">ID/PW 찾기</a>
		                    </section>
	
	                	</form>
            		</c:when>
            		
            		<%-- 로그인이 되었을때 --%>
            		<c:otherwise>
            			<article class="login-area">
            				
            				<a href="#">
            					<img src="/resources/images/user.png" id="memberProfile">
            				</a>
            				
            				<div class="my-info">
            					<div>
            						<a href="#" id="nickname">${sessionScope.loginMember.memberNickname}</a>
            						<a href="/member/logout" id="logoutBtn">로그아웃</a>
            					</div>
            					
            					<p></p>
            				</div>
            				
            			</article>
            			
            			
            		</c:otherwise>
            	</c:choose>
            
            

            </section>

        </section>
       

    </main>
    
    <jsp:include page="/WEB-INF/views/common/footer.jsp" />

	<!-- main.js 추가 -->
	<script src="/resources/js/main.js"></script>

</body>
</html>

main.js


const loginFrm = document.getElementById("loginFrm");

const memberEmail = document.querySelector("#loginFrm input[name='memberEmail']");
const memberPw = document.querySelector("#loginFrm input[name='memberPw']");

if(loginFrm != null){
    // 로그인 시도를 할 때
    loginFrm.addEventListener("submit", e => {

        // 이메일이 입력되지 않은 경우
        // 문자열.trim() : 문자열 좌우 공백 제거
        if(memberEmail.value.trim().length == 0){
            alert("이메일을 입력해주세요.");

            memberEmail.value = ""; // 잘못 입력된 값(공백) 제거
            memberEmail.focus(); // 이메일 input태그에 초점을 맞춤

            e.preventDefault(); // (기본이벤트 제거 : 제출 못하게하기)
            return; 
        }


        // 비밀번호가 입력되지 않은 경우
        if(memberPw.value.trim().length == 0){
            alert("비밀번호를 입력해주세요.");

            memberPw.value = ""; // 잘못 입력된 값(공백) 제거
            memberPw.focus(); // 이메일 input태그에 초점을 맞춤

            e.preventDefault(); // 제출 못하게하기
            return; 
        }


    });
}




// -----------------------------------------------

// fetch API : 웹 브라우저에서 서버로 HTTP 요청을 하게해주는 최신 인터페이스

/** 
 * fetch(url)
 * .then(response => response.json() / response.text())          // 파싱
 * .then(data => console.log(data))                              // 데이터 가공
 * .catch(error => console.log(error));
 * 
 * 첫 번째 then() 함수는 서버 요청에 대한 응답이 왔을때 실행됨
 * - 응답받은 데이터가 반환되는 값이 단순 자료형 1개면 text(),
 * 객체(Map)면 json() 으로 파싱(구문해석)한 후 다음 then() 함수로 넘겨준다.
 * 
 * 
 * 두 번째 then() 함수는 response.json()/text()으로 상황에 맞게
 * 데이터가 파싱 완료되면 실행.
 * 파싱된 데이터가 전달되며, 이 값을 로직에 맞게 가공한다.
 * 
 * 
*/

// 닉네임이 일치하는 회원의 전화번호 조회
const inputNickname = document.getElementById("inputNickname");
const btn1 = document.getElementById("btn1");
const result1 = document.getElementById("result1");

btn1.addEventListener("click", () => {

    // fetch API를 이용해서 ajax
    // GET 방식 요청 (파라미터를 쿼리스트링으로 추가)

    // Promise : 비동기 함수 호출 또는 연산이 완료되었을 때
    //         이후에 처리할 함수나 에러를 처리하기 위한
    //         함수를 설정하는 모듈
    //         -> 비동기 연산의 최종 결과 객체

    fetch("/selectMemberTel?nickname=" + inputNickname.value)
    .then( resp => resp.text() ) // 응답 객체(자료형 1일때)를 문자열 형식으로 파싱
    .then( data => {
        // 데이터 가공
        console.log(data);
        result1.innerText = data;
    })
    .catch( err => console.log(err) );

});

AjaxController.java

package edu.kh.project.member.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import edu.kh.project.member.model.service.AjaxService;

@Controller // 요청/응답 제어 + bean 등록
public class AjaxController {
	
	@Autowired
	private AjaxService service;
	
	
	// ** 닉네임으로 전화번호 조회
	@GetMapping("/selectMemberTel")
	@ResponseBody
	public String selectMemberTel(/*@RequestParam("nickname")*/ String nickname) {
							// 쿼리스트링에 담겨있는 파라미터
		
		
		// return 리다이렉트 / 포워드 -> 새로운 화면 보임 (동기식)
		
		// return 데이터 -> 데이터를 요청한 곳으로 반환 (비동기식)
		
		// @ResponseBody
		// -> Controller의 결과로 데이터를 반환할 때 사용하는 어노테이션
		
		return service.selectMemberTel(nickname);
	}
	
}

AjaxService.java

package edu.kh.project.member.model.service;

public interface AjaxService {

	/** 닉네임으로 전화번호 조회
	 * @param nickname
	 * @return tel
	 */
	String selectMemberTel(String nickname);

}

AjaxServiceImpl.java

package edu.kh.project.member.model.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import edu.kh.project.member.model.dao.AjaxDAO;

@Service // 서비스임을 명시 + bean 등록
public class AjaxServiceImpl implements AjaxService{
	
	@Autowired
	private AjaxDAO dao;

	// 닉네임으로 전화번호 조회
	@Override
	public String selectMemberTel(String nickname) {
		
		return dao.selectMemberTel(nickname);
	}

}

AjaxDAO.java

package edu.kh.project.member.model.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository // DB 연결 의미 + bean 으로 등록
public class AjaxDAO {

	@Autowired // bean 중에서 타입이 같은 객체를 DI(의존성 주입)
	private SqlSessionTemplate sqlSession;

	// 닉네임으로 전화번호 조회
	public String selectMemberTel(String nickname) {
		
		return sqlSession.selectOne("ajaxMapper.selectMemberTel", nickname);
	}
	
}

ajax-mapper.xml

  • xml 파일 생성 방법 !

    mappers 파일 > ctrl + N





-> 새로운 mapper 생성시 꼭 이 'mapper 파일 추가' 작업 필요!

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="ajaxMapper">


	<!-- parameterType : 전달 받은 파라미터의 자료형 작성
		-> 선택사항으로, 작성 안하면 TypeHandler가 알아서 처리
	 -->
	 
	 <!--    자바      마이바티스
			 int    -> _int
			 String -> string
	  -->
	  
	  
	<!-- 닉네임으로 전화번호 조회 -->
	<select id="selectMemberTel" resultType="string">
		SELECT MEMBER_TEL FROM "MEMBER"
		WHERE MEMBER_NICKNAME = #{nickname}
		AND MEMBER_DEL_FL = 'N'
	</select>
	

</mapper>


유저일 입력 후 조회 버튼 클릭 시, 화면은 전환되지 않고 전화번호만 나옴
이번유저 입력 후 조회 버튼 클릭 시, 화면은 전환되지 않고 전화번호만 나옴


0개의 댓글