커뮤니티 - 로그인 기능 구현 (23.07.04~05)

·2023년 7월 5일
0

Server

목록 보기
7/35
post-thumbnail

시작하기 전에!

이번 포스팅부터는 서버 관련 다양한 기능을 구현하여 나만의 커뮤니티를 만들어 보려고 한다. 구현하는 기능별로 차근차근 글을 써 볼 예정이다. 😎


📝 로그인 기능 구현

💡 Oracle

🔎 ash 커뮤니티 계정

CREATE TABLE "MEMBER" (
   "MEMBER_NO"   NUMBER   PRIMARY KEY,
   "MEMBER_EMAIL"   VARCHAR2(50) NOT NULL,
   "MEMBER_PW"   VARCHAR2(30) NOT NULL,
   "MEMBER_NICK"   VARCHAR2(30) NOT NULL,
   "MEMBER_TEL"   CHAR(11) NOT NULL,
   "MEMBER_ADDR"   VARCHAR2(500),
   "PROFILE_IMG"   VARCHAR2(200),
   "ENROLL_DT"   DATE DEFAULT SYSDATE ,
   "SECESSION_FL"   CHAR(1)   DEFAULT 'N'
);


COMMENT ON COLUMN "MEMBER"."MEMBER_NO" IS '회원 번호';
COMMENT ON COLUMN "MEMBER"."MEMBER_EMAIL" IS '회원 이메일(아이디)';
COMMENT ON COLUMN "MEMBER"."MEMBER_PW" IS '회원 비밀번호';
COMMENT ON COLUMN "MEMBER"."MEMBER_NICK" IS '회원 닉네임(중복x)';
COMMENT ON COLUMN "MEMBER"."MEMBER_TEL" IS '전화번호(- 미포함)';
COMMENT ON COLUMN "MEMBER"."MEMBER_ADDR" IS '회원 주소';
COMMENT ON COLUMN "MEMBER"."PROFILE_IMG" IS '회원 프로필 이미지';
COMMENT ON COLUMN "MEMBER"."ENROLL_DT" IS '회원 가입일';
COMMENT ON COLUMN "MEMBER"."SECESSION_FL" IS '탈퇴여부(Y:탈퇴, N:미탈퇴)';


-- 회원 번호 시퀀스
CREATE SEQUENCE SEQ_MEMBER_NO;

INSERT INTO MEMBER
VALUES(SEQ_MEMBER_NO.NEXTVAL, 'user01@kh.or.kr', 'pass01!', 
    '유저일', '01012341234', '04540,,서울특별시 강남구 테헤란로 14길 6 5층',
     NULL, DEFAULT, DEFAULT);
     
COMMIT;


-> 위 코드를 수행하면 사진 속과 같은 테이블이 생성된다.


💡 eclipse

🔎 JDBCTemplate.java

package edu.kh.community.common;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class JDBCTemplate {
	
	/* DB 연결, JDBC 자원 반환, 트랜잭션 제어 같은
	 * 반복적으로 사용되는 JDBC 관련 코드를 모아둔 클래스
	 * 
	 * getConnection() 메소드
	 * 
	 * close( Connection | Statement | ResultSet ) 메소드
	 *  --> PreparedStatement는 Statement의 자식이므로
	 *  	매개변수 다형성으로 한 번에 처리
	 *  
	 * commit(Connection)
	 * rollback(Connection)
	 * 
	 * 
	 */

	// 필드
	
	private static Connection conn; // 초기값 null
	// private static Connection conn = null; // 초기값 null
	
	// 메소드
	
	// DB 연결 정보를 담고 있는 Connection 객체 반환 메소드
	public static Connection getConnection() {
		
		try {
			
//			JNDI(Java Naming and Directory Interface API)
//			- 디렉토리에 접근하는 데 사용하는 Java API
//			- 애플리케이션(프로그램, 웹앱)은 JNDI를 이용해서 파일 또는 서버 Resource를 찾울 수 있음
			
			Context initContext = new InitialContext();
			
//			servers -> context.xml 파일 찾기
			Context envContext = (Context)initContext.lookup("java:/comp/env");
			
//			DBCP 세팅의 <Resource> 태그를 찾아 DataSource 형식의 객체로 얻어오기
//			DataSource : Connection Pool을 구현하는 객체(Connection 얻어오기 가능)
			DataSource ds = (DataSource)envContext.lookup("jdbc/oracle");
			
			conn = ds.getConnection();
			conn.setAutoCommit(false);
			
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		return conn;
	}

	// close() 메소드 작성

	// Connection 반환 메소드
	public static void close(Connection conn) {
		try {
		
			// 참조하는 Connection이 있으면서 닫혀 있지 않은 경우
			if(conn != null && !conn.isClosed()) {
				// conn.isClosed() : 닫혀 있으면 true
				conn.close();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	// Statement(부모), PreparedStatement(자식) 반환 메소드(다형성 적용)
	public static void close(Statement stmt) {
		try {
		
			// 참조하는 Statement가 있으면서 닫혀 있지 않은 경우
			if(stmt != null && !stmt.isClosed()) {
				// stmt.isClosed() : 닫혀 있으면 true
				stmt.close();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}

	// ResultSet 반환 메소드
	public static void close(ResultSet rs) {
		try {
		
			// 참조하는 ResultSet이 있으면서 닫혀 있지 않은 경우
			if(rs != null && !rs.isClosed()) {
				// rs.isClosed() : 닫혀 있으면 true
				rs.close();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	// 트랜잭션 제어
	
	// commit 메소드
	public static void commit(Connection conn) {
		try {
		
			// 참조하는 Connection이 있으면서 닫혀 있지 않은 경우
			if(conn != null && !conn.isClosed()) {
				conn.commit();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}	
	
	// rollback 메소드
	public static void rollback(Connection conn) {
		try {
		
			// 참조하는 Connection이 있으면서 닫혀 있지 않은 경우
			if(conn != null && !conn.isClosed()) {
				conn.rollback();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}	
	
}

🔎 context.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
--><!-- The contents of this file will be loaded for each web application -->
<Context workDir="C:\workspace\5_Server\community\src\main\webapp\WEB-INF\jspwork">

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
    
    <!-- DataBase Connection Pool(DBCP) 
    
    	- 미리 DB와 연결되어 있는 Connection을 일정 개수 만들어 두고
    	  클라이언트 요청 시 만들어 둔 Connection을 빌려주고
    	  요청 처리 완료 시 다시 반환받는 기법
		    
		- 장점 :
		1) 미리 Connection을 만들어 두기 때문에 요청 시 속도가 빠름
		   (예전에는 그때 그때 만들어서 오래 걸림)
		
		2) Connection 개수에 제한을 두어 DB에 과도하게 요청되는 경우를 방지
		
    -->
    
    <!-- DBCP 세팅 -->   
   <Resource 
      name="jdbc/oracle" 
      auth="Container"
      type="javax.sql.DataSource" 
      driverClassName="oracle.jdbc.OracleDriver"
      url="jdbc:oracle:thin:@localhost:1521:xe"
      username="community_ash" 
      password="community1234" 
      maxTotal="50"      
      maxIdle="10"
      maxWaitMillis="-1"
    />
    <!-- 
       name : JNDI 이름 Context의 lookup()을 사용하여 자원을 찾을때 사용한다. java:comp/env 디렉터리에서 찾을 수 있다.
       auth : 자원 관리 주체 지정(Application 또는 Container)
       type : Resource의 타입 지정
       driverClassName : JDBC 드라이버 클래스 이름.
       maxTotal : DataSource 에서 유지할 수 있는 최대 커넥션 수    
       maxIdle : 사용되지 않고 풀에 저장될 수 있는 최대 커넥션의 개수. 음수일 경우 제한이 없음
       maxWaitMillis : 커넥션 반납을 기다리는 시간(-1은 반환될 때 까지 기다림)
     --> 
  
</Context>

🔎 index.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="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>KH 커뮤니티</title>

    <link rel="stylesheet" href="resources/css/main-style.css">

    <script src="https://kit.fontawesome.com/4dca1921b4.js" crossorigin="anonymous"></script>
</head>
<body>

    <main>
        <header>
            
            <!-- 클릭 시 메인페이지로 이동하는 로고 -->
            <section>
                <a href="#">
                    <img src="resources/images/logo.jpg" id="home-logo">
                </a>
            </section>

            <!-- header의 두번째 자식 div -->
            <section>
                <article class="search-area">

                    <!-- form 내부 input 태그 값을 서버 또는 페이지로 전달 -->
                    <form action="#" name="search form">

                        <!-- fieldset : form 내부에서 input을 종류별로 묶는 용도로 많이 사용 -->
                        <fieldset>

                            <input type="search" id="query" name="query" 
                                placeholder="검색어를 입력해주세요" autocomplete="off">

                            <!-- 검색 버튼 -->
                            <button type="submit" id="search-btn" class="fa-solid fa-magnifying-glass"></button>
                            
                        </fieldset>

                    </form>
                </article>
            </section>
            <section></section>
        </header>

        <nav>

            <ul>
                <li><a href=#>공지사항</a></li>
                <li><a href=#>자유 게시판</a></li>
                <li><a href=#>질문 게시판</a></li>
                <li><a href=#>FAQ</a></li>
                <li><a href=#>1:1문의</a></li>
            </ul>
        </nav>

        <section class="content">
            <section class="content-1">
            	loginMember : ${sessionScope.loginMember}
            	
            	<hr>
            	
            	message : ${sessionScope.message}
            </section>
            <section class="content-2">
            
            	<!-- if - else -->
            	<c:choose>

					<%-- choose 내부에는 jsp 주석만 사용 --%>
            		<c:when test="${ empty sessionScope.loginMember }">
            		
            			<!-- 절대경로 : /community/member/login -->
		            	<!-- 상대경로 (index.jsp) 기준 -->
		                <form action="member/login" method="post" name="login-form">
		        
		                    <!-- 아이디(이메일)/비밀번호/로그인 버튼 영역 -->
		                    <fieldset id="id-pw-area">
		                        <section>
		                            <input type="text" name="inputEmail" placeholder="아이디(이메일)">
		                            <input type="password" name="inputPw" placeholder="비밀번호">
		                        </section>
		                        <section>
		                            <button>로그인</button>
		                        </section>
		                    </fieldset>
		        
		                    <!-- 회원가입, ID/PW 찾기 영역 -->
		                    <article id="signup-find-area">
		                        <a href="#">회원가입</a>
		                        <span>|</span>
		                        <a href="#">ID/PW 찾기</a>
		                    </article>
		
		                    <label>
		                        <input type="checkbox">아이디 저장
		                    </label>
		                    
		                </form>
		                
            		</c:when>
            		
            		<%-- 로그인이 되어 있는 경우 --%>
            		<c:otherwise>
            			<article class='login-area'>
            			
            				<!-- 회원 프로필 이미지 -->
            				<a href="#">
            					<img src="/community/resources/images/user.png" id="member-profile">
            				</a>

							<!-- 회원 정보 + 로그아웃 버튼 -->
							<div class="my-info">
								<div>
									<a href="#" id="nickname">${sessionScope.loginMember.memberNickname}</a>

									<a href="#" id="logout-btn">로그아웃</a>
								</div>
								
								<p>
									${sessionScope.loginMember.memberEmail}
								</p>
							</div>
							
            			</article>
            		</c:otherwise>

            	</c:choose>
            
            </section>
        </section>
    </main>
    <footer>
        <p>Copyright &copy; KH Information Educational Institute M-Class</p>

        <article>
            <a href="#">프로젝트 소개</a>
            <span>|</span>
            <a href="#">이용약관</a>
            <span>|</span>
            <a href="#">개인정보처리방침</a>
            <span>|</span>
            <a href="#">고객센터</a>
        </article>
    </footer>

</body>
</html>

🔎 main-style.css

*{box-sizing: border-box;}

/* div{ border : 1px solid black;} */

main{
    width: 1140px;
    margin: auto;
}

/* header */
header{
    height:200px;
    display: flex; /* flex 형식으로 변경 -> 내부 요소를 유연하게 정렬/배치 */
}

header > section:nth-of-type(1){
    flex-basis : 15%;
}

header > section:nth-of-type(2){
    flex-basis : 70%;
}

header > section:nth-of-type(3){
    flex-basis : 15%;
}

/* 로고 */

header > section:nth-child(1){
    display: flex;
    justify-content: center;
    align-items: center;
}

#home-logo{
    width :120px;
    height :auto;
    /* 이미지는 width/height 둘 중 하나만 지정 시
    나머지 방향의 크기가 같은 비율로 지정됨
    */
}

/* 검색 스타일 */
header > section:nth-last-of-type(2){
    display: flex;
    justify-content: center;
    align-items: center;
}

.search-area{
    width:500px;
}

.search-area fieldset{
    padding : 2px;
    margin : 0;
    border:2px solid #455ba8;
    border-radius: 5px;
    display: flex;
}

#query{
    padding : 10px;
    font-size:18px;
    font-weight: bold;

    width :92%;

    border : none;

    /* outline : input 태그에 포커스가 맞춰 졌을 때
                이를 표현하기 위한 바깥선
                (테두리 보다 바깥에 존재) */
    outline:0; /* none도 가능 */

}

#search-btn{
    width :8%;
    cursor : pointer;
    font-size : 1.2em;
    color : #455ba8;
    background-color: white;
    border :0; /* none도 가능 */
}

/* nav */
nav{
    height: 50px;

    position: sticky;
    /* sticky : 스크롤이 임계점(최상단)에 도달했을 때 내 화면에 스티커 처럼 붙임
        - 평소에는 static(기본 position 상태)
          임계점 도달 시 fixed(화면 특정 위치에 고정)

        * top, bottom, left, right 속성이 필수로 작성되어야 함
        -> 임계점 도달 시 어느 위치에 부착할지를 지정해야되기 때문에
    */

    top : 0; /* 최상단에 붙임 */

    background-color: white;
    border-bottom:2px solid black;
}

 /* nav 스타일 */
 nav > ul{
    display: flex;
    list-style: none;
    padding: 0;
    margin: 0;
    height: 100%;
}

nav li{
    flex : 0 1 150px;
    /* 팽창, 수축, 기본값 */
}

nav a{
    display: block;
    height: 100%;

    text-align: center;

    /* 글자를 세로 가운델 지정하는 방법 */
    /* line-height: 48px; */
    padding : 11px 0;

    /* 밑줄 없애기 */
    text-decoration: none;
    font-size: 18px;
    font-weight: bold;
    color :black;

    border-radius: 5px;
    transition-duration: 0.1s;
}

nav a:hover{
    background-color: #455ba8;
    color: white;
    
}

/* content */
.content{
    height : 800px;
    display: flex;
}

.content-1{ flex-basis: 70%;}
.content-2{ flex-basis: 30%;}

/* login 스타일 */
form[name="login-form"]{
    height: 140px;
    padding: 10px;

    display: flex;

    /* 중심 축을 세로로 변경 */
    flex-direction: column;

    /* 중심 축에 대한 정렬(세로 가운데 정렬) */
    justify-content: center;
}

form[name="login-form"] > label{
    font-size: 14px;
    margin-top: 5px;
}

#id-pw-area{
    margin: 0;
    padding: 0;

    border: 1px solid #ddd;

    display: flex;
}

/* id/pw input이 담긴 영역 */
#id-pw-area > section:first-child{
    flex-basis: 75%;
    display: flex;
    flex-direction: column;
}

#id-pw-area > section:last-child{
    flex-basis: 25%;
    display: flex;
    justify-content: center;
}

#id-pw-area input{
    border: 0;
    border-right: 1px solid #ddd;
    flex-basis: 50%;
    padding: 5px;
    outline: 0;
    margin: 0;
}

#id-pw-area input:first-child{
    border-bottom: 1px solid #ddd;
}

#id-pw-area input:focus{
    border: 2px solid #455ba8;
}

/* 로그인 버튼 */
#id-pw-area button{
    width: 100%;
    border: 0;   
    background-color: white;
    cursor: pointer;
}

#id-pw-area button:hover{
    background-color: #455ba8;
    color: white;
}

/* 회원가입, ID/PW 찾기 영역 */
#signup-find-area{
    margin-top: 10px;
    padding-left: 5px;
}

#signup-find-area > a{
    color: black;
    text-decoration: none;
    font-size: 14px;
}

#signup-find-area > span{
    padding: 0 10px;
        /* 상하, 좌우 */
}

/* 로그인 성공 시 화면 */
.login-area{
	height: 120px;
	padding: 30px 10px;
	margin: 10px 0px;
	
	border: 1px solid #ddd;
	border-radius: 10px;
	display: flex;
	align-items: center;
}

.login-area > a{ /* 이미지를 감싸고 있는 a 태그*/
	display: block;
	width: 70px;
	height: 70px;
	border: 3px solid #ddd;
	border-radius: 50%;
}

#member-profile{
	width: 64px;
	height: 64px;
	border-radius: 50%;
}

.my-info{ /* 회원 정보 + 로그아웃 영역 */
	width: 100%;
	height: 100%;
	margin-left: 20px;
}

.my-info > div{
	display: flex;
	justify-content: space-between;
}

#nickname{ /* 닉네임 */
	color: black;
	text-decoration: none;
	font-size: 20px;
	font-weight: bold;
	letter-spacing: -3px;
}

#logout-btn{ /* 로그아웃 버튼 */
	color: gray;
	text-decoration: none;
	font-size: 14px;
	font-weight: bold;
	
	border: 2px solid gray;
	padding: 3px 5px;
	border-radius: 10px;
}

#logout-btn:hover{
	color: white;
	background-color: gray;
}

.my-info > p{ /* 이메일 */
	margin: 0;
	color: gray;
}

/* footer */
footer{
    height :200px;
    background-color: #a3add342;
}

/* footer 스타일 */
footer{
    background-color: #a3add342;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

footer p{
    font-weight: bold;
}

footer > article > *{
    font-size: 14px;
}

footer a{
    color: black;
    text-decoration: none;
}

footer span{
    padding: 0 10px;
}


-> lib 폴더에 lombok.jar 파일을 추가해 준다.

🔎 Member.java

package edu.kh.community.member.model.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter // getter 자동 추가
@Setter // setter 자동 추가
@ToString // toString 자동 추가
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드 초기화하는 매개변수 생성자

//Lombok 라이브러리
//- VO(Value Object) 또는 DTO(Data Transfer Object)에
//작성되는 공통 코드(getter/setter/생성자)를 자동 추가해 주는 라이브러리

public class Member {

	private int memberNo;
	private String memberEmail;
	private String memberPw;
	private String memberNickname;
	private String memberTel;
	private String memberAddress;
	private String profileImage;
	private String enrollDate;
	private String secessionFlag;
	
}



-> Encoding Filter.java의 경로는 이러하다.

🔎 Encoding Filter.java

package edu.kh.community.common.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;


/* 필터(Filter)
 * 
 * - 클라이언트 요청 시 생성되는
 * 	 HttpServletRequest, HttpServletResponse가
 * 	 요청을 처리하는 Servlet에 도달하기 전에
 *   특정 코드를 수행하는 클래스
 * 
 * 	 [요청 흐름]
 * 	 클라이언트 -> 요청 -> HttpServletRequest -> 필터 -> 요청 처리 Servlet
 * 					   HttpServletResponse
 * 					   		생성
 *
 * - 여러 필터를 만들어 연쇄적으로 연결하여 수행할 수 있다. (FilterChain)
 * 
 * */

// @WebFilter("url패턴")
// - 해당 클래스를 필터 클래스로 등록
// - url 패턴에 일치하는 요청이 있을 경우 해당 요청을 필터링함

// filterName 속성 : 필터의 이름 지정, 필터 순서 지정 시 사용
// urlPatterns 속성 : 요청 주소 패턴

// / == /community == 최상위 주소
// * == 모든
// /* == 최상위 주소 하위 모든 == 모든 요청

@WebFilter(filterName = "encodingFilter", urlPatterns="/*")
public class EncodingFilter extends HttpFilter implements Filter {

	// 서버 실행 시 또는 필터 코드 변경 시 필터 객체가 자동 생성되는데
	// 그때, 필터에 필요한 내용을 초기화하는 메소드
	public void init(FilterConfig fConfig) throws ServletException {
		System.out.println("문자 인코딩 필터 초기화");
		
	}

	// 서버 실행 중 필터 코드가 변경되어
	// 기존 필터를 없애야 할 때 수행되는 메소드
	public void destroy() {
		System.out.println("문자 인코딩 필터 파괴");
	}

	// 필터 역할을 수행하는 메소드
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		// ServletRequest == HttpServletRequest의 부모 타입
		// ServletResponse == HttpSErvletResponse의 부모 타입
		// -> 필요 시 다운캐스팅
		
		// 모든 요청의 문자 인코딩을 UTF-8로 설정
		request.setCharacterEncoding("UTF-8");
		
		// 모든 응답의 문자 인코딩을 UTF-8로 설정
		response.setCharacterEncoding("UTF-8");
		
		// 연결된 다음 필터 수행(없으면 Servlet 수행)
		chain.doFilter(request, response);
	}

}

🔎 CreateXML.java

package edu.kh.community.sql;

import java.io.FileOutputStream;
import java.util.Properties;
import java.util.Scanner;

public class CreateXML {
   public static void main(String[] args) {

      try {
         Scanner sc = new Scanner(System.in);

         System.out.print("생성할 XML 이름 입력 : ");

         String fileName = sc.nextLine();

         Properties prop = new Properties();

         String filePath = "src/main/java/edu/kh/community/sql/";

         prop.storeToXML(new FileOutputStream(filePath + fileName), fileName);

         System.out.println(fileName + "생성 성공");

      } catch (Exception e) {
         e.printStackTrace();
      }

   }
}

🔎 member-sql.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>member-sql.xml</comment>

	<!-- 로그인(비밀번호, 탈퇴 여부 제외)  -->
	<entry key="login">
		SELECT MEMBER_NO, MEMBER_EMAIL, MEMBER_NICK, MEMBER_TEL,
	    	MEMBER_ADDR, PROFILE_IMG,
	    	TO_CHAR(ENROLL_DT, 'YYYY-MM-DD HH24:MI:SS') AS ENROLL_DT
	    FROM MEMBER
		WHERE MEMBER_EMAIL = ?
		AND MEMBER_PW = ?
		AND SECESSION_FL = 'N'
	</entry>



</properties>

🔎 LoginServlet.java

package edu.kh.community.member.controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import edu.kh.community.member.model.service.MemberService;
import edu.kh.community.member.model.vo.Member;

@WebServlet("/member/login")
public class LoginServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
//		Post 방식 요청 시 문자 인코딩이 서버 기본값으로 지정
//		-> 한글 깨짐 -> 문자 인코딩 변경 필요
//		req.setCharacterEncoding("UTF-8");
		
//		* 모든 doPost() 메소드에 인코딩 변경 코드를 작성해야 함.. (귀찮다)
//		-> 모든 요청(전달 방식 가리지 않음) 시 req, resp에 문자 인코딩을 UTF-8로 변경
//		-> 필터(Filter)
		
//		전달된 파라미터 변수에 저장
		String inputEmail = req.getParameter("inputEmail");
		String inputPw = req.getParameter("inputPw");
		
		System.out.println(inputEmail);
		System.out.println(inputPw);
		
		// 파라미터를 VO에 세팅(롬복 확인)
		Member mem = new Member();
		mem.setMemberEmail(inputEmail);
		mem.setMemberPw(inputPw);
		
		try {
			// 서비스 객체 생성
			MemberService service = new MemberService();
			
			// 이메일, 비밀번호가 일치하는 회원을 조회하는 서비스 호출 후 결과 반환
			Member loginMember = service.login(mem);
			
			// 로그인 성공/실패에 따른 처리 코드
			
			// *** 로그인 ***
			// ID/PW가 일치하는 회원 정보를 Session Scope에 세팅하는 것
			
			// Session 객체 얻어오기
			HttpSession session = req.getSession();
			
			if(loginMember != null) { // 성공
				
				// 회원 정보를 Session에 세팅
				session.setAttribute("loginMember", loginMember);
				
				// 특정 시간 동안 요청이 없으면 세션 만료
				session.setMaxInactiveInterval(3600); // 3600초 == 1시간
				// -> 초 단위로 작성
				
			} else { // 실패
				
				session.setAttribute("message", "아이디 또는 비밀번호가 일치하지 않습니다.");
				
			}
			
			// 클라이언트 요청 -> 서버 요청 처리(Servlet) -> 응답 화면 만들어 줘(JSP 위임)
			
			// 1. forward(요청 위임)
			// - Servlet으로 응답 화면 만들기가 불편하기 때문에
			//   JSP로 req, resp 객체를 위임하여
			//	 요청에 대한 응답 화면을 대신 만듦
			
			// *** 화면이 변경되어도 요청 주소가 유지된다 ***
			
			// ex) 아이스 아메리카노 주세요 -> 	주문 받음		-> 바리스타가 만든 커피
			//		   클라이언트           캐셔(Servlet)		응답 결과(JSP)
			
			
			// 2. Redirect(재요청)
			// - 현재 Servlet에서 응답 페이지를 만들지 않고
			//	 응답 페이지를 만들 수 있는
			//	 다른 요청의 주소로 클라이언트를 이동시킴(재요청)
			
			// 클라이언트 재요청
			// -> 기존 HttpServletRequest/Response 제거
			// -> 새로운 HttpServletRequest/Response 생성
			
			// ---> 리다이렉트 시 request 객체가 유지되지 않기 때문에
			//		특정 데이터를 전달하거나 유지하고 싶으면
			//	    Session 또는 application 범위에 세팅해야 한다!
			
			// CGV 카페
			// ex) 팝콘 주세요		->	팝콘 파는 위치 알려 줌	-> (클) 팝콘 파는 곳으로 이동
			//     클라이언트			  캐셔(Servlet)		  클라이언트의 다른 주소 재요청
			
			resp.sendRedirect( req.getContextPath() );
			// req.getContextPath() : 최상위 주소 (/community)
			
			// forward
			// req.getRequestDispatcher("../index.jsp").forward(req, resp);
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


💡 출력 화면 (로그인)

  • 아이디, 비밀번호 입력 후 '로그인' 버튼을 클릭했을 때

-> 콘솔창에 입력된 아이디, 비밀번호 값이 출력된다.

-> 홈페이지에는 로그인한 유저의 닉네임, 이메일 주소와 로그아웃 버튼이 출력된다.

profile
풀스택 개발자 기록집 📁

0개의 댓글