국비 77 - 로그인(스프링)

냐아암·2023년 8월 11일
0

국비

목록 보기
88/114

📍 Model

데이터 전달용 객체

  • 데이터를 K : V 형식으로 담아서 전달
  • 기본적으로 request scope -> @SessionAttribute 어노테이션과 함께 사용 시 Session scope
    🔑 SessionAttribute - 여러 개일 경우 배열 형태로 작성

📍 RedirectAttributes

리다이렉트 시 데이터를 request scope으로 전달할 수 있게 하는 객체

🔑 잠시 Session에 추가


MemberController

@SessionAttributes({"loginMember"})
// alt shift J
	/** 로그인 요청 처리(찐)
	 * @return 메인페이지 redirect 주소
	 */
	@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 inputMember : 커맨드 객체(필드에 파라미터 담겨있음)
		
		// @RequestHeader(value="referer") String referer
		// -> 요청 HTTP header에서 "referer"(이전주소) 값을 얻어 와
		//	  매개변수 String referer에 저장

		// Model : 데이터 전달용 객체
		// -> 데이터를 K : V 형식으로 담아서 전달
		// -> 기본적으로 request scope
		// -> @SessionAttributes 어노테이션과 함께 사용 시 Session scope
		
		// @RequestParam(value="saveId") String saveId
		// -> name 속성 값이 "saveId"인 파라미터를 전달받아 저장
		// -> required=false : 필수 아님(null 허용)
		// (주의) required 속성 미작성 시 기본값 true
		// -> 파라미터가 전달되지 않는 경우 주의
		
		// HttpServletResponse resp : 서버 -> 클라이언트 응답 방법을 가지고 있는 객체
		
		// System.out.println(saveId);
		
		// 로그인 서비스 호출
		Member loginMember = service.login(inputMember);
		
		// DB 조회 결과 확인
		// System.out.println(loginMember);
		
		// 로그인 결과에 따라 redirect 경로를 다르게 지정
		String path = "redirect:"; 
		
		if(loginMember != null) { // 로그인 성공 시
			path += "/"; // 메인 페이지로 redirect
			
			// Session에 로그인한 회원 정보 추가
			// Servlet -> HttpSession.setAttribute(key, value);
			// Spring -> Model + @sessionAttributes
			
			// 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(이전 주소)
			
			/* redirect(재요청) 시
			 * 기존 요청(request)이 사라지고
			 * 새로운 요청(request)을 만들게 되어
			 * 
			 * redirect된 페이지에서는 이전 요청이 유지되지 않는다.
			 * -> 유지하고 싶으면 어절 수 없이 Session 이용
			 * 
			 * [Spring]
			 * 이런 상황을 해결하기 위한 객체
			 * RedirectAttributes를 제공
			 * 
			 * RedirectAttributes
			 * - 리다이렉트 시 데이터를 request scope로 전달할 수 있게 하는 객체
			 *   
			 * 응답 전 : request scope
			 * 
			 * 응답 중 : session scope로 잠시 이동
			 * 
			 * 응답 후 : request scope
			 * */
			
			// addFlashAttribute : 잠시 Session에 추가
			ra.addFlashAttribute("message", "아이디 또는 비밀번호가 일치하지 않습니다.");
		}
		
		return path;
	}

📍 Service Interface 사용하는 이유

  • 프로젝트 규칙성 부여
  • 클래스간 결합도 약화 -> 객체 지향적 설계
  • Spring AOP 사용

MemberService

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

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

// Service Interface 사용하는 이유

// 1. 프로젝트의 규칙성을 부여하기 위해서

// 2. 클래스간의 결합도를 약화시키기 위해서(객체 지향적 설계)

// 3. Spring AOP 사용을 위해서



public interface MemberService {

	/** 로그인 서비스
	 * @param inputMember(email, pw)
	 * @return email, pw가 일치하는 회원 정보 또는 null
	 */
	Member login(Member inputMember);

}

📍 @Service

  • 비즈니스 로직을 처리하는 클래스라고 명시
  • Bean으로 등록하는 어노테이션

📍 @AutoWired

  • 작성된 필드와 Bean으로 등록된 객체 중 타입이 일치하는 Bean을 해당 필드에 자동 주입 == DI

MemberServiceImpl

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 // Service layer
		 // 비즈니스 로직(데이터 가공, 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) {
		
		// 암호화 추가 예정
		// 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;
	}

}

📍 @Repository

영속성 관련 클래스(Bean으로 등록)

📍 SqlSessionTemplate

MemberDAO

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

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

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

@Repository // Persistence layer - 영속성 관련 클래스
//				(파일, DB 관련 클래스) + Bean으로 등록 (== spring이 객체로 만들어 둠)
public class MemberDAO {

	// SqlSessionTemplate(마이바티스 객체) 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);
	}

}

📍 resultMap

rs 컬럼명과 dto의 필드명이 같지 않을 때, 이를 매핑시켜 SELECT 시 자동으로 담기게 하는 역할

📍 SQL 관련 속성

  • parameterType : 전달받은 값의 자료형(별칭 사용 가능)
  • resultType : select 결과를 담아서 반환할 자료형(필드명과 컬럼명이 같은 경우에만 사용 가능)

📍 #{변수명 | 필드명} ≒ PreparedStatement

📍 ${변수명 | 필드명} ≒ Statement

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=""/> -->
    
    <!-- 
    
    	resultMap
    	- SELECT 조회 결과(ResultSet) 컬럼명과
    	  컬럼 값을 옮겨 담을 DTO의 필드명이 같지 않을 때
    	  이를 매핑 시켜 SELECT 시 자동으로 담기게 하는 역할
    	  
    	- 속성
    	  type : 연결할 DTO(패키지명 + 클래스명 또는 별칭)
    	  id : 만들어진 resultMap을 지칭할 식별명(이름)
    	  
    	  <id> 태그 : PK 역할 컬럼 - 필드 매핑
    	  <result> 태그 : <id> 제외 나머지
    
     -->
     
     <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"/>
     	<result property="authority" column="AUTHORITY"/>
     	
     </resultMap>
    
    
    <!-- 
    	SQL 관련 태그 속성
    	
    	- parameterType : 전달받은 값의 자료형
    					  기본: 패키지명 + 클래스명
    					  별칭 : Mybatis 별칭 또는 사용자 지정 별칭
    					  
    	- parameterMap : (사용 안 함)
    	
    	- resultType : select 결과를 담아서 반환할 자료형
    				   단, DTO를 작성할 경우 필드명 == 컬럼명인 경우에만 사용 가능
    				   				  memberNo != MEMBER_NO
    
    	- resultMap : select 결과의 컬럼명과 
    				  결과를 저장할 dto 필드명이 다를 경우
    				  이를 알맞게 매핑(연결)시켜주는 <resultMap> id 작성
     -->
    					<!-- mybatis-config.xml에서 별칭 지정함 -->
    					
    <!-- 
    	** 마이바티스에서 전달 받은 값을 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>
    
</mapper>
profile
개발 일지

0개의 댓글