1cm

자바 프로그래밍_Day_109_회원정보 관련 본문

국비지원_Java/Java Programming_2

자바 프로그래밍_Day_109_회원정보 관련

dev_1cm 2022. 2. 18. 22:11
반응형

 

2022. 01. 19

 

 

 

- 회원가입 (복습 참고)

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<!--  
    mapping 속성에 해당하는 요청에 대해 location 속성에 지정 된 디렉터리(폴더)로 매핑을 시켜준다.
-->
<resources mapping="/resources/**" location="/resources/" />
<!--
    사용자로 부터 /js/jquery-3.6.0.js 라는 요청이 오면 이것을 /resources/js/jquery-3.6.0.js로 매핑시킨다.
-->
<resources mapping="/js/**" location="/resources/js/" />

 

- servlet-context.xml

resource로 시작하는 요청이나 js로 시작하는 요청은 Dispatcher Servlet에서 직접 처리하는 것이 아닌 location 경로와 매핑하여 바로 내려줄 수 있게끔 설정해줄 수 있는 속성이다.

 

 

- 회원가입 로직 구현

@PostMapping("/member/enroll")
public String enroll(@ModelAttribute Member member) {

    log.info(member.toString());

    int result = service.save(member);

    return "member/enroll";
}

int -> 영향받은 행의 개수(정수형)

 

MemberService.java -> 추상 메소드 추가

package com.kh.mvc.member.model.service;

import com.kh.mvc.member.model.vo.Member;

public interface MemberService {

	Member login(String id, String password);

	int save(Member member);

}

 

 

MemberServiceImpl.java -> 메소드 추가

@Override
public int save(Member member) {
    int result = 0;

    if(member.getNo() != 0) {
        // update
    } else {
        // insert
        result = mapper.insertMember(member);

    }


    return result;
}

 

 

MemberMapper.java

@Mapper
public interface MemberMapper {
	
	// 아래 메소드를 호출 시 실제 mapper.xml에 있는 쿼리문을 수행한 다음 결과를 리턴해주는 인터페이스의 구현체가 만들어진다.
	Member findMemberById(@Param("id") String id);
	
	int insertMember(Member member);

 

 

 

- 트랜젝션 처리

기존 : result값을 보고 commit, rollback 진행 -> 예외 발생시 실제 앞에서 작업했던 것이 rollback이 되어야 함.

@Override
public int save(Member member) {
    int result = 0;

    if(member.getNo() != 0) {
        // update
    } else {
        // insert
        result = mapper.insertMember(member);
    }

//		if(true) {
//			throw new RuntimeException();
//		}

    return result;

}

 

 

mybatis-context.xml

- 트랜젝션 매니저 또한 빈으로 등록하여 사용함

<!-- 
    트랜잭션 처리 방법
    1. 트랜젝션 매니저 등록하기
    2. @Transactional을 사용해서 트랜젝션 처리가 될 수 있도록 <tx:annotation-driven />태그를 설정 파일(servlet-context.xml)에 등록한다.
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    p:dataSource-ref="dataSource"
/>

 

 

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"	
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

트랜젝션 처리를 위한 태그네임스페이스인 xmlns:tx와 스키마 정보 추가,

 

 

 

어노테이션을 사용해서 트랜젝션을 처리하겠다는 설정을 위한 tx:annotation-driven 추가

<!--
    DB 관련 설정은 root-context.xml에서 import하는 mybatis-context.xml에 작성했지만 
    <tx:annotation-driven />는 실제 트랜젝션을 적용할 빈들이 등록되는 ApplicationContext 설정 파일에 작성해야 한다.
-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

 

 

MemberServiceImpl.java 에서 @Transactional을 붙여준 뒤 테스트 진행하게 되면

@Override
@Transactional
public int save(Member member) {
    int result = 0;

    if(member.getNo() != 0) {
        // update
    } else {
        // insert
        result = mapper.insertMember(member);
    }

		if(true) {
			throw new RuntimeException();
		}

    return result;

}

 

예외 발생 시 rollback된다 -> 데이터 조회 X

- aop로 활용한 것

만약 어노테이션 기반으로 사용 안할 시 메소드 하나하나씩 설정을 해줘야 한다.

 

 

 

 

class에 붙여서 사용도 가능한데, 그렇게 되면 전체 적용이 되지만 필요한 메소드에만 붙여줘서 트랜젝션 처리가 될 수 있게 해주는 것이 더 좋다.

 

마찬가지로 Service의 인터페이스에도 붙여서 사용도 가능하다.

 

 

 

 

- Controller

@PostMapping("/member/enroll")
public ModelAndView enroll(ModelAndView model, @ModelAttribute Member member) {

    log.info(member.toString());

    int result = service.save(member);

    if(result > 0) {
        model.addObject("msg", "회원가입이 정상적으로 완료되었습니다.");
        model.addObject("location", "/");
    } else {
        model.addObject("msg", "회원가입을 실패하였습니다.");
        model.addObject("location", "/member/enroll");			
    }

    model.setViewName("common/msg");

    return model;
}

 

 

 

- 패스워드 암호화

 

MVN Repository - Spring Security Core, Web, Config 라이브러리들을 pom.xml에 추가

 

dependency추가 후 버전을 ${}로 관리해준다.

<!-- Spring-security -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${org.springsecurity-version}</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${org.springsecurity-version}</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${org.springsecurity-version}</version>
</dependency>

 

 

암호화는 Application Context에서 bean으로 만들어서 활용

 

 

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

	<!-- 암호화를 위한 BCryptPasswordEncoder 등록 -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

</beans>

BCrypt(비크립트) -> 암호화 방식

-> password 생성 시 랜덤한 솔트를 침(1234를 암호화 시 매번 다르게 생성(솔트:salt 를 친다))

 

키스트레칭 -> 암호화 과정을 여러번 거치는 기법 (암호화된 값을 암호화 -> 또 암호화)

 

 

> root-context.xml에 security-context.xml를 import 시켜준다.

<import resource="security-context.xml"/>

 

 

 

> MemberServiceImpl.java

	@Autowired
	private BCryptPasswordEncoder passwordEncoder;

 

 

 

save하기 전에 패스워드를 암호화 시켜준다.

@Override
@Transactional
public int save(Member member) {
    int result = 0;

    if(member.getNo() != 0) {
        // update
    } else {
        // 패스워드 암호화
        member.setPassword(passwordEncoder.encode(member.getPassword()));

        // insert
        result = mapper.insertMember(member);
    }

//		if(true) {
//			throw new RuntimeException();
//		}

    return result;

}

 

 

그 뒤 로그인에서 syso로 확인해보면 (System.out.println())

	@Override
	public Member login(String id, String password) {
		
		Member member = null;

//		member = dao.findMemberById(session, id);
		member = mapper.findMemberById(id);
		
//		System.out.println(mapper.findAll());
		
		System.out.println(passwordEncoder.encode(password));
		System.out.println(member.getPassword());
		System.out.println(passwordEncoder.matches(password, member.getPassword()));
		
			
//	if (member != null && member.getPassword().equals(passwordEncoder.encode(password))) {
//		return member;			
//	} else {
//		return null;
//	}

	}

 

원문은 전부 1234이지만

암호화된 결과는 다 다르게 뜬다. -> 기존 if 방식 사용하기 어려움

-> matches에 기존 암호, 암호화된 password를 넘겼을 때 결과값이 true가 반환된다.(복호화 가능) / 값이 다를경우 false 반환

 

 

@Override
public Member login(String id, String password) {

    Member member = null;

//		member = dao.findMemberById(session, id);
    member = mapper.findMemberById(id);

//		System.out.println(mapper.findAll());

//		System.out.println(passwordEncoder.encode(password));
//		System.out.println(member.getPassword());
//		System.out.println(passwordEncoder.matches(password, member.getPassword()));

//	if (member != null && member.getPassword().equals(passwordEncoder.encode(password))) {
//		return member;			
//	} else {
//		return null;
//	}

    return member != null && 
            passwordEncoder.matches(password, member.getPassword()) ? member : null;

}

-> 삼항 연산자로 작성

 

 

 

 

* log4j.xml

- priority value = debug로 변경

<!-- Root Logger -->
<root>
    <priority value="debug" />
    <appender-ref ref="console" />
</root>

 

 

 

 

- 중복검사

(json 형태로 데이터 내려받기)

 

enroll.jsp에서 if문 잠시 주석 처리

 

 

 

 

 > MemberController.java

@PostMapping("/member/idCheck")
public String idCheck(@RequestParam("userId") String userId) {

    log.info("{}", userId);

    return "안녕하세요 " + userId + "님";
}

일단 문자열로 데이터를 넘겨주기

-> 404에러 : Controller에서 리턴해주는 것이 문자열을 리턴해주기 때문 : 문자열:view이름 - Dispatcher Servlet이 view이름을 찾으려고 하는데 존재하지 않기 때문

 

 

 

@GetMapping("/member/idCheck")
@ResponseBody
public String idCheck(@RequestParam("userId") String userId) {

    log.info("{}", userId);

    return "Hello " + userId;
}

 

-> 반환하는 리턴값이 view 이름이 아닌 응답하는 body쪽에 데이터를 바로 담아서 출력해주기 위해 @ResponseBody를 붙여준다.

 : 데이터(문자열)를 응답의 body에 담아서 리턴해줌

* 한글이 깨져서 영어로 바꿔줌

 

 

 

 

json 테스트를 할 수 있는 컨트롤러 생성

// json 테스트할 수 있는 컨트롤러 생성
@GetMapping("/member/jsonTest")
@ResponseBody
public String jsonTest() {

    return "Hello";
}

 

 

 

/*
 * @ResponseBody
 *   - 일반적으로 컨트롤러 메소드의 반환형이 String 타입일 경우 뷰의 이름을 찾아서 반환한다.
 *   - @ResponseBody 붙은 String 반환은 해당 요청을 보낸 클라이언트에 전달할 데이터를 의미한다.
 *   
 * jackson 라이브러리
 *   - 자바 객체를 JSON 형태의 데이터로 변환하기 위한 라이브러리이다.(GSON, jsonSimple)
 *   - 스프링에서는 jackson 라이브러리를 추가하고 @ResponseBody을 사용하면 리턴되는 객체를 자동으로 JSON으로 변경해서 응답한다.
 *   
 * @RestController
 *   - 해당 컨트롤러의 모든 메소드에서 데이터를 반환하는 경우 사용한다. 
 *   - @Controller와 @ResponseBody를 합쳐놓은 역할을 수행한다.
 */

 

 

@GetMapping("/member/jsonTest")
@ResponseBody
public Object jsonTest() {

    return new Member("mrhong", "1234", "홍길동");
}

객체 형태 -> 에러 발생

 

@GetMapping("/member/jsonTest")
@ResponseBody
public Object jsonTest() {
    Map<String, String> map = new HashMap<>();

    map.put("hi", "hello");

//		return new Member("mrhong", "1234", "홍길동");

    return map;

}

map 형태도 마찬가지로 에러가 발생한다. - 406에러 발생

 

 

mvnrepository에서 jackson databind -> pom.xml에 추가시켜줌

<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
</dependency>

json 관련된 라이브러리이다.

-> 자바 객체를 json 문자열 형태로 변환해주는 라이브러리. (gson과 비슷)

 

 

// json 테스트할 수 있는 컨트롤러 생성
@GetMapping("/member/jsonTest")
@ResponseBody
public Object jsonTest() {
//		Map<String, String> map = new HashMap<>();
//		
//		map.put("hi", "hello");
//		
//		return map;

    return new Member("mrhong", "1234", "홍길동");

}

그리고 재실행시 json 문자열 형태로 잘 넘어오는 것을 확인할 수 있다.

 

 

@RestController -> 모든 메소드에서 데이터로 반환하는 경우 최상단에 달아준다.

 

 

 

 

 

- idCheck 구현

// 아이디 중복체크
@PostMapping("/member/idCheck")
@ResponseBody
public String idCheck(@RequestParam("userId") String userId) {

    log.info("{}", userId);

    return "Hello " + userId;
}

->

@PostMapping("/member/idCheck")
@ResponseBody
public Object idCheck(@RequestParam("userId") String userId) {
    Map<String, Boolean> map = new HashMap<>();

    log.info("{}", userId);

    map.put("duplicate", false);

    return map;

}

->

public Object idCheck(@RequestParam("userId") String userId) {
    Map<String, Boolean> map = new HashMap<>();

    log.info("{}", userId);

    map.put("duplicate", service.isDuplicateID(userId));

    return map;
}

isDuplicaateId 메소드가 없어서 발생하는 오류 해결을 위해 메소드들 생성

 

 

 

> MemberService.java

//@Transactional
public interface MemberService {

	Member login(String id, String password);

	int save(Member member);

	Boolean isDuplicateID(String userId);

}

 

 

> MemberServiceImpl.java

@Override
public Boolean isDuplicateID(String id) {

    return mapper.findMemberById(id) != null;
}

 

 

enroll의 주석 해줬던 if부분을 풀어준다.

 

 

 

 > MemberController.java

// 아이디 중복체크
@PostMapping("/member/idCheck")
//	@ResponseBody
//	@ResponseBody를 사용하지 않고 ResponseEntity를 사용하는 방법
public ResponseEntity<Map<String, Boolean>> idCheck(@RequestParam("userId") String userId) {
    Map<String, Boolean> map = new HashMap<>();

    log.info("{}", userId);

    map.put("duplicate", service.isDuplicateID(userId));

    return new ResponseEntity<Map<String,Boolean>>(map, HttpStatus.OK);
}

 

 

 

pom.xml에 테스트코드 추가

    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>2.6.0</version>
    </dependency>
    
	<dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>

 

 

 

MemberControllerTest.java 생성

 

 

> MemberControllerTest.java

package com.kh.mvc.member.controller;

import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
	locations = {
			"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml",
			"file:src/main/webapp/WEB-INF/spring/root-context.xml"
	}
)
class MemberControllerTest {
	@Autowired
	MemberController controller;
	
	MockMvc mockMvc;

	@Test
	public void create() throws Exception {
		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
		
		mockMvc.perform(get("/member/enroll"))
			   .andExpect(view().name("member/enroll"))
			   .andExpect(status().isOk());
	}
	
	@Test
	public void idCheckTest() throws Exception {
		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
		
		mockMvc.perform(post("/member/idCheck").param("userId", "ismoon"))
			   .andExpect(status().isOk())
			   .andExpect(jsonPath("$.duplicate", is(true)));
	}

}

 

 

 

 

 

 

 

전체 실습 코드

 

 

> MemberController.java

package com.kh.mvc.member.controller;

import java.util.HashMap;
import java.util.Map;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.ModelAndView;

import com.kh.mvc.member.model.service.MemberService;
import com.kh.mvc.member.model.service.MemberServiceImpl;
import com.kh.mvc.member.model.vo.Member;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
//@RestController
//Model 객체에 loginMember라는 키값으로 객체가 추가되면 해당 객체를 세션 스코프에 추가하는 어노테이션이다.
@SessionAttributes("loginMember")
public class MemberController {
/*
	// controller가 처리하게 될 요청에 대해 정의함(URL, Method 등)
//	@RequestMapping(value = "/login", method = {RequestMethod.GET})
	@GetMapping("/login")
	// 요청을 처리할 메소드 작성
	public String login() {
		
		log.info("login() - 호출");
		
		return "home";
	}
	
	사용자의 파라미터를 전송받는 방법
	
	1. HttpServletRequest를 통해서 전달(전송)받기(기존 JSP/Servlet 방식)
		- 메소드의 매개 변수로 HttpServletRequest를 작성하면 메소드 실행 시 스프링 컨테이너가 자동으로 객체를 인자로 주입해준다.
	@PostMapping("/login")
	public String login(HttpServletRequest request) {
		String id = request.getParameter("id");
		String password = request.getParameter("password");
		
		log.info("login() - 호출 : {} {}", id, password);
		
		return "home";
	}
	
	2-1. @RequestParam 어노테이션을 통해서 전송받기
		- 스프링에서 조금 더 간편하게 파라미터를 받아올 수 있는 방법 중 하나이다.
		- 내부적으로는 Request 객체를 이용해서 데이터를 전송받는 방법이다.
		- 단, 매개변수의 이름과 name 속성의 값이 동일하게 설정된 경우 자동으로 주입된다.
			(어노테이션을 사용하는 것이 아니기 때문에 defaultValue 설정이 불가능하다.)
		
	@RequestMapping(value = "login", method= {RequestMethod.POST})
//	public String login(@RequestParam("id") String id, @RequestParam("password") String password) {
	public String login(String userId, String password) {
	
		// name 속성의 갑소가 매개변수의 명이 같으면 어노테이션 생략이 가능
		public String login(String id, String password) {
		
		log.info("login() - 호출 : {} {}", id, password);
		
		return "home";
	}
	
	2-2. @RequestParam에 default 값 설정
		- defaultValue 속성을 사용하면 파라미터 name 속성에 값이 없을 경우 기본값을 지정할 수 있다.
	@PostMapping("/login")
	public String login(@RequestParam("id") String id, 
						@RequestParam(value = "password", defaultValue = "0000") String password) {
	
		log.info("login() - 호출 : {} {}", id, password);

		return "home";
	}
	
	2-3. @RequestParam에 실제 존재하지 않는 파라미터를 받으려고 할 때
		- 파라미터 name 속성에 없는 값이 넘어올 경우 에러가 발생한다.
		- @RequestParam(required = false)로 지정하면 null 값을 넘겨준다.
		- 단, defaultValue를 설정하면 defaultValue에 설정된 값으로 넘겨준다. (에러가 발생하지 않음)
	
	@PostMapping("/login")
	public String login(@RequestParam("id") String id, 
						@RequestParam(value = "password") String password,
//						@RequestParam(value = "address", required = false)String address) {
						@RequestParam(value = "address", defaultValue = "서울특별시") String address) {
	
		log.info("login() - 호출 : {} {} {}", new Object[] {id, password, address});

		return "home";
	}
	
	3. @PathVariable 어노테이션을 통해서 전송받기 (restAPI시 많이 사용)
		- URL 패스상에 있는 특정 값을 가져오기 위해 사용하는 방법이다.
		- REST API를 사용할 때, 요청 URL 상에서 필요한 값을 가져오는 경우 주로 사용한다.
		- 매핑 URL에 {}로 묶는다면, {} 안의 값을 Path Variable로 사용하고 요청 시 실제 경로상의 값을 해당 Path Variable로 받겠다는 의미이다.
		- 매핑 URL에 {} 안의 변수명과 매개변수의 변수명이 동일하다면 @PathVariable의 괄호는 생략이 가능하다.
			(어노테이션 자체는 생략이 안된다. @RequestParam인지 @PathVariable인지 알 수 없음)
			
	@GetMapping("/member/{id}")
//	public String findMember(@PathVariable("id") String id) {
	public String findMember(@PathVariable String id) {
		
		log.info("Member ID : {}", id);
		
		return "home";
	}
	
	4. @ModelAttribute 어노테이션을 통해서 전송받기 (파라미터가 많을 경우)
		- 요청 파라미터가 많은 경우 객체 타입으로 파라미터를 넘겨받는 방법이다.
		- 스프링 컨테이너가 기본 생성자를 통해서 객체를 생성하고
			파라미터 NAME 속성의 값과 동일한 필드명을 가진 필드에 값을 주입해준다.
		- 단, 기본 생성자와 Setter가 존재해야 한다.
		- @ModelAttribute 어노테이션을 생략해도 객체로 매핑된다.
		
	@PostMapping("/login")
//	public String login(@ModelAttribute Member member) {
	public String login(Member member) {
		
		log.info("{}, {}", member.getId(), member.getPassword());
		
		return "home";
	}
	
 */
	
	@Autowired
	private MemberService service;
	
/*
 *  로그인 처리
 *  1. HttpSession과 Model(Controller가 처리하고 만들어진 데이터를 Dispatcher Servlet에게 전달해주는 것) 객체
 *  	1) Model
 *  		- 컨트롤러에서 데이터를 뷰로 전달하고자 할 때 사용하는 객체이다.
 *		    - 전달하고자 하는 데이터를 맵 형태(key, value)로 담을 수 있다.
 *		    - Model 객체의 Scope는 기본적으로 Request이다. (하나의 요청을 받아서 응답될때까지만 유지)
 *

	@PostMapping("/login")
	public String login(
			HttpSession session, Model model,
			@RequestParam("id")String id, @RequestParam String password) {
		
		log.info("{}, {}", id, password);
		
		Member member = service.login(id, password);
		
		if (member != null) {
			session.setAttribute("loginMember", member);
			
			/*
			 *  return "home";
			 *  	- forwarding 방식으로 ViewResolver에 의해 /WEB-INF/views/home.jsp로 forwarding 한다.
			 *  
			 *  return "redirect:/";
			 *  	- redirect 방식으로 여기서 리턴 한 경로로 브라우저에서 다시 요청을 하도록 반환한다.
			 *
			 *
			return "redirect:/";
		} else {
			model.addAttribute("msg", "아이디나 비밀번호가 일치하지 않습니다.");
			model.addAttribute("location","/");

			return "common/msg"; // /WEB-INF/views/common/msg.jsp로 forwarding 한다.
		}
	}
	
	// 로그아웃 처리
	@PostMapping("/logout")
	public String logout(HttpSession session) {
		
		session.invalidate();
		
		return "redirect:/";
	}
	
	2. @SessionAttributes과 ModelAndView 객체
		1) @SessionAttributes("키값")
			- Model 객체에 "키값"에 해당하는 Attribute를 Session Scope까지 범위를 확장시킨다.
		2) ModelAndView
	 	    - 컨트롤러에서 뷰로 전달할 데이터와 뷰에 정보를 담는 객체이다.
	 	    - addAttribute()가 아닌 addObject() 메소드를 통해서 데이터를 담을 수 있다.

*/	
	@RequestMapping(value = "/login", method = {RequestMethod.POST})
	public ModelAndView login(ModelAndView model,
			@RequestParam("id") String id, @RequestParam("password") String password) {
		
		log.info("{}, {}", id, password);
		
		Member loginMember = service.login(id, password);
		
		if(loginMember != null) {
			model.addObject("loginMember", loginMember);
			model.setViewName("redirect:/");
		} else {
			model.addObject("msg", "아이디나 비밀번호가 일치하지 않습니다.");
			model.addObject("location", "/");
			model.setViewName("common/msg");
		}
		return model;
	}
	
	// 로그아웃 처리 (SessionStatus 객체 사용)
	@PostMapping("/logout")
	public String logout(SessionStatus status) {
		
		log.info("status.isComplete() : {}", status.isComplete());
		
		// SessionStatus 객체의 setComplete() 메소드로 세션 스코프로 확장된 객체들을 지워준다.
		status.setComplete();
		
		log.info("status.isComplete() : {}", status.isComplete());
		
		return "redirect:/";
	}
	
	// 회원가입
	@GetMapping("/member/enroll")
	public String enroll() {
		log.info("회원 가입 페이지 요청");
		
		return "member/enroll";
	}
	
	@PostMapping("/member/enroll")
	public ModelAndView enroll(ModelAndView model, @ModelAttribute Member member) {
		
		log.info(member.toString());
		
		int result = service.save(member);
		
		if(result > 0) {
			model.addObject("msg", "회원가입이 정상적으로 완료되었습니다.");
			model.addObject("location", "/");
		} else {
			model.addObject("msg", "회원가입을 실패하였습니다.");
			model.addObject("location", "/member/enroll");			
		}
		
		model.setViewName("common/msg");
		
		return model;
	}
	
	
	/*
	 * @ResponseBody
	 *   - 일반적으로 컨트롤러 메소드의 반환형이 String 타입일 경우 뷰의 이름을 찾아서 반환한다.
	 *   - @ResponseBody 붙은 String 반환은 해당 요청을 보낸 클라이언트에 전달할 데이터를 의미한다.
	 *   
	 * jackson 라이브러리
	 *   - 자바 객체를 JSON 형태의 데이터로 변환하기 위한 라이브러리이다.(GSON, jsonSimple)
	 *   - 스프링에서는 jackson 라이브러리를 추가하고 @ResponseBody을 사용하면 리턴되는 객체를 자동으로 JSON으로 변경해서 응답한다.
	 *   
	 * @RestController
	 *   - 해당 컨트롤러의 모든 메소드에서 데이터를 반환하는 경우 사용한다. 
	 *   - @Controller와 @ResponseBody를 합쳐놓은 역할을 수행한다.
	 */
	// json 테스트할 수 있는 컨트롤러 생성
	@GetMapping("/member/jsonTest")
	@ResponseBody
	public Object jsonTest() {
//		Map<String, String> map = new HashMap<>();
//		
//		map.put("hi", "hello");
//		
//		return map;
		
		return new Member("mrhong", "1234", "홍길동");
	}
	
	
	// 아이디 중복체크
	@PostMapping("/member/idCheck")
//	@ResponseBody
//	@ResponseBody를 사용하지 않고 ResponseEntity를 사용하는 방법 (jackson 라이브러리 추가하고 사용)
	public ResponseEntity<Map<String, Boolean>> idCheck(@RequestParam("userId") String userId) {
		Map<String, Boolean> map = new HashMap<>();
		
		log.info("{}", userId);
		
		map.put("duplicate", service.isDuplicateID(userId));
		
		return new ResponseEntity<Map<String,Boolean>>(map, HttpStatus.OK);
	}
	
}

 

 

 

 

 

> MemberServiceImpl.java

package com.kh.mvc.member.model.service;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.kh.mvc.member.model.dao.MemberMapper;
import com.kh.mvc.member.model.vo.Member;

//@Service("빈 ID")
@Service
//@Transactional
public class MemberServiceImpl implements MemberService {
	
	@Autowired
	private MemberMapper mapper;
	
	@Autowired
	private BCryptPasswordEncoder passwordEncoder;

	
//	@Autowired
//	private SqlSession session;
	
	@Override
	public Member login(String id, String password) {
		
		Member member = null;

//		member = dao.findMemberById(session, id);
		member = mapper.findMemberById(id);
		
//		System.out.println(mapper.findAll());
		
//		System.out.println(passwordEncoder.encode(password));
//		System.out.println(member.getPassword());
//		System.out.println(passwordEncoder.matches(password, member.getPassword()));
			
	//	if (member != null && member.getPassword().equals(passwordEncoder.encode(password))) {
	//		return member;			
	//	} else {
	//		return null;
	//	}
		
		return member != null && 
				passwordEncoder.matches(password, member.getPassword()) ? member : null;

	}

	@Override
	@Transactional
	public int save(Member member) {
		int result = 0;
		
		if(member.getNo() != 0) {
			// update
		} else {
			// 패스워드 암호화
			member.setPassword(passwordEncoder.encode(member.getPassword()));

			// insert
			result = mapper.insertMember(member);
		}
		
//		if(true) {
//			throw new RuntimeException();
//		}
	
		return result;

	}

	@Override
	public Boolean isDuplicateID(String id) {

		return mapper.findMemberById(id) != null;
	}

}

 

 

 

 

 > mybatis-context.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:c="http://www.springframework.org/schema/c"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
		destroy-method="close"
		p:driverClassName="${db.driver}"
		p:url="${db.url}"
		p:username="${db.username}"
		p:password="${db.password}"
	/>
	
	<bean id="mybatisConfig" class="org.apache.ibatis.session.Configuration"
		p:jdbcTypeForNull="NULL"
	/>
	
	<!-- 
		p:configLocation="classpath:mybatis-config.xml"
		p:configuration-ref="mybatisConfig"
	 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
		p:configuration-ref="mybatisConfig"
		p:mapperLocations="classpath:mappers/**/*.xml"
		p:typeAliasesPackage="com.kh.mvc.*.model.vo"
		p:dataSource-ref="dataSource"
	/>
	
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"
		c:sqlSessionFactory-ref="sqlSessionFactory"
	/>
	
	<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"
		p:basePackage="com.kh.mvc.*.model.dao"
	/>
	
		
	<!-- 
		트랜잭션 처리 방법
		1. 트랜젝션 매니저 등록하기
		2. @Transactional을 사용해서 트랜젝션 처리가 될 수 있도록 <tx:annotation-driven />태그를 설정 파일(servlet-context.xml)에 등록한다.
	-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dataSource"
	/>
	
</beans>

 

 

 

 

 

> servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"	
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<!--  
		mapping 속성에 해당하는 요청에 대해 location 속성에 지정 된 디렉터리(폴더)로 매핑을 시켜준다.
	-->
	<resources mapping="/resources/**" location="/resources/" />
	<!--
		사용자로 부터 /js/jquery-3.6.0.js 라는 요청이 오면 이것을 /resources/js/jquery-3.6.0.js로 매핑시킨다.
	-->
	<resources mapping="/js/**" location="/resources/js/" />


	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<!-- @Component 클래스를 찾아서 Bean으로 등록 -->
	<context:component-scan base-package="com.kh.mvc" />
	
	<!-- 
	<annotation-driven />, <context:component-scan base-package="com.kh.mvc" />을 사용하지 않을 경우 
	아래와 같이 HandlerMapping과 Controller를 명시적으로 bean으로 등록해야 한다.
	
	각 요청을 수행할 Controller들의 클래스를 빈으로 등록하고
	<beans:bean id="loginController" class="com.kh.spring.member.controller.LoginController"/>
	<beans:bean id="logoutController" class="com.kh.spring.member.controller.LogoutController"/>
	
	HandlerMapping에 각각의 Controller들을 매핑해야 한다.	
	<beans:bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<beans:property name="mappings">
			<beans:props>
				<beans:prop key="login.me">loginController</beans:prop>
				<beans:prop key="logout.me">logoutController</beans:prop>						
			</beans:props>
		</beans:property>
	</beans:bean>
	* 잘 안씀
	-->

	<!--
		DB 관련 설정은 root-context.xml에서 import하는 mybatis-context.xml에 작성했지만 
		<tx:annotation-driven />는 실제 트랜젝션을 적용할 빈들이 등록되는 ApplicationContext 설정 파일에 작성해야 한다.
	-->
	<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
	
	
</beans:beans>

 

 

 

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

	<!-- 암호화를 위한 BCryptPasswordEncoder 등록 -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

</beans>

 

반응형
Comments