💎 JSP
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>로그인 페이지</title>
<link rel="stylesheet" href="/resources/css/member/login.css">
</head>
<body>
<main>
<section class="logo-area">
<a href="/">
<img src="/resources/images/logo.jpg">
</a>
</section>
<form action="/member/login" method="post" id=loginFrm>
<section class="input-box">
<input type="text" name="memberEmail" placeholder="Email" value="${cookie.saveId.value}" />
</section>
<section class="input-box">
<input type="password" name="memberPw" placeholder="Password" />
</section>
<button class="login-btn">Login</button>
<%-- 쿠키에 saveId가 있는 경우--%>
<c:if test="${ !empty cookie.saveId.value}">
<%-- chk 변수 생성(page scope)--%>
<c:set var="chk" value="checked"/>
</c:if>
<div class="saveId-area">
<input type="checkbox" name="saveId" id="saveId" ${chk}>
<label for="saveId"><i class="fas fa-check"></i>아이디 저장</label>
</div>
<p class="text-area">
<a href="#">회원가입</a>
|
<a href="#">ID/PW 찾기</a>
</p>
</form>
</main>
<%-- main.js추가 --%>
<script src="/resources/js/main.js"></script>
</body>
</html>
💎 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=> {
// alert("로그인")
// preventDefault : form ,a 기본 이벤트 제거
// e.preventDefault();
// 이메일이 입력되지 않은 경우
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;
}
})
}
📣 querySelector()
: 괄호 속에 제공한 선택자와 일치하는 문서 내 첫 번째 Element를 반환 일치하는 요소가없다면 Null 반환
💎 Spring
🔎 MemberController
@PostMapping("/login")
public String login(Member inputMember, Model model
, @RequestHeader(value="referer") String referer
, @RequestParam(value="saveId", required=false) String saveId
, HttpServletResponse resp
, RedirectAttributes ra) {
// 로그인 서비스 호출
Member loginMember = service.login(inputMember);
// DB 조회 결과 확인
// System.out.println(loginMember);
// 로그인 결과에 따라 리다이렉트 경로를 다르게 지정
String path = "redirect:";
if(loginMember !=null) { // 로그인 성공 시
path +="/"; // 메인페이지로 리다이렉트
// 1) model에 로그인한 회원 정보 추가
model.addAttribute("loginMember", loginMember);
// -> 현재는 request scope
// 2) 클래스 위에 @SessionAttributes 추가
// -> session scope로 변경
// -------------------- 아이디 저장 --------------------------
/* Cookie란?
* - 클라이언트 측(브라우저)에서 관리하는 파일
*
* - 쿠키파일에 등록된 주소 요청 시마다
* 자동으로 요청에 첨부되어 서버로 전달됨.
*
*
* - 서버로 전달된 쿠키에
* 값 추가, 수정, 삭제 등을 진행한 후
* 다시 클라이언트에게 반환
*
*/
/* Session
* - 서버가 클라이언트의 정보를 저장하고 있음 (↑ 쿠키와의 차이점)
*/
// 쿠키 생성(해당 쿠키에 담을 데이터를 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; // HTTP Header - referer(이전주소)
// addFlashAttribute : 잠시 Session에 추가
ra.addFlashAttribute("message", "아이디 또는 비밀번호가 일치하지 않습니다.");
}
return path;
}
Member inputMember : 커멘드 객체(필드에 파라미터 담겨있음)
@RequestHeader(value="referer") String referer
-> 요청 HTTP header에서 "referer"(이전주소) 값을 얻어와
매개 변수 String referer에 저장
Model : 데이터 전달용 객체
-> 데이터를 K : V 형식으로 담아서 전달
-> 기본적으로 request scope
-> @SessionAttributes 어노테이션과 함께 사용 시 Session scope
@RequestParam(value="saveId") String svaeId
-> name 속성 값이 "saveId"인 파라미터를 전달 받아 작성
-> required=false : 필수 아님 (null 허용)
(주의) required 속성 미작성 시 기본 값 true
-> 파라미터가 전달되지 않는 경우 주의
HttpServletResponse resp : 서버 -> 클라이언트 응답 방법을 가지고 있는 객체
🔎 MemberService
public interface MemberService {
/** 로그인 서비스
* @param inputMember (email.pw)
* @return email, pw가 일치하는 회원 정보 또는 null
*/
Member login(Member inputMember)
🔎 MemberServiceImpl
@Service // Service Layer
// 비즈니스 로직(데이터가공, dao 호출, 트랜잭션 제어) 처리하는 클래스라 명시
// + Bean 등록하는 어노테이션
public class MemberServiceImpl implements MemberService {
// org.slf4j.Logger : 로그를 작성할 수 있는 객체
// org.slf4j.LoggerFactor
private Logger logger = LoggerFactory.getLogger(MemberServiceImpl.class);
// 현재 클래스명.class
// @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) {
// 로그 출력
logger.info("MemberService.login() 실행"); // 정보 출력
logger.debug("memberEmail : "+ inputMember.getMemberEmail() );
logger.warn("이건 경고 용도");
logger.error("이건 오류 발생 시");
// 암호화 추가 예정
System.out.println("암호화 확인 : " + bcrypt.encode(inputMember.getMemberPw()));
// bcrypt 암호화는 salt가 추가되기 떄문에 계속 비밀번호가 바뀌게되어 DB에서 비교 불가능!!
// -> 별도로 제공해주는 matches(평문, 암호문)을 이용해 비교
// dao 메소드 호출
Member loginMember = dao.login(inputMember);
if(loginMember !=null) { // 아이디가 일치하는 회원이 조회된 경우
// 입력한 pw, 암호화된 pw같은지 확인
// 같을 경우
if(bcrypt.matches(inputMember.getMemberPw(),
loginMember.getMemberPw())) {
// 비밀번호를 유지하지 않기 위해서 로그인 정보에서 제거
loginMember.setMemberPw(null);
} else { // 다를 경우
loginMember = null; // 로그인 실패처럼 만듬
}
}
return loginMember;
}
🔎 MemberDAO
@Repository // Persistence Layer, 영속성 관련 클래스
// (파일, DB관련 클래스) + Bean 등록(== spring이 처리하는 클래스)
public class MemberDAO {
// SqlSessionTempate (마이바티스 객체) DI
@Autowired // 등록된 Bean 중에서 SqlSessionTemplate 타입의 Bean을 주입
private SqlSessionTemplate sqlSession;
/** 로그인 DAO
* @param inputMember
* @return 회원정보 또는 null
*/
public Member login(Member inputMember) {
// 마이바티스를 이용해서 1행 조회(selectOne)
// sqlSession.selectOne("namespace값.id값, 전달할 값")
// -> namespace가 일치하는 Mapper에서
// id가 일치하는 SQL 구문을 수행 후
// 결과를 1행(dto, 기본 자료형) 반환
return sqlSession.selectOne("memberMapper.login",inputMember);
}
🔎 member-mapper.xml
<mapper namespace="memberMapper">
<!-- namespace : 공간(영역, 지역, 태그)의 이름 -->
<!-- ** mapper 파일 생성 시 아래 태그 반드시 삭제!!!! ** -->
<!--<cache-ref namespace=""/> -->
<!--
resultMap
- SELECT 조회 결과(ResultSet) 컬럼명과
컬럼 값을 옮겨 담을 DTO의 필드명이 같지 않을 때
이를 매핑 시켜 SELECT시 자동으로 담기게 하는 역할
- 속성
type : 연결할 DTO (패키지명 + 클래스명 또는별칭)
id : 만들어진 resultMap을 지칭할 식별명(이름)
<id> 태그 : PK 역할 컬럼 - 필드 매핑
<result> 태그 : <id>제외 나머지
-->
<!-- <resultMap type="원래는 DTO클래스네임까지 경로를 다 작성해야함 근데 마이바티스에서 별칭 Member로 해놨기때문에 ↓ *Member*로 적을수있음" id="resultMap을 지칭할 별칭지정"></resultMap> -->
<resultMap type="Member" id="member_rm">
<!-- 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
- rseultMap : select 결과의 컬럼명과
결과를 저장할 DTO 필드명이 *다를 경우*
이를 알맞게 매핑(연결)시켜주는 <resultMap> id 작성
-->
<!--
** 마이바티스에서 전달 받은 값을 SQL에 작성하는 방법 **
#{변수명 | 필드명} : PreparedStatement
: SQL에 값 대입 시 양쪽에 ' '붙여서 대입
${변수명 | 필드명} : Statement
: SQL에 값 대입 시 양쪽에 아무것도 붙이지 않음
사용 예시)
test1 = "user01"
test2 = MEMBER_EMAIL
- MEMBER_EMAIL이 'user01'인 회원 조회
SELECT * FROM MEMBER WHERE ${test2} = #{test1}
MEMBER_EMAIL = 'user01'
-->
<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}
</select>