클라우드 융합 Full-stack 웹 개발자 양성과정/Spring

Spring - 로그인, 로그아웃, 회원가입

thesunset 2022. 12. 20. 22:25

📌스프링 컨테이너와 빈에 대해

- 스프링 컨테이너란?

 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할

자바 객체를 스프링에서는 빈(Bean)이라고 부름

 

스프링 컨테이너는 IoC와 DI의 원리를 이용 , 싱글톤을 유지

 

- 참고 

출처 : KH

1. 로그인

< header.jsp >

<form action="login.me" method="post">
    <!-- Modal body -->
    <div class="modal-body">
        <label for="userId" class="mr-sm-2">ID : </label>
        <input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name="userId"> <br>
        <label for="userPwd" class="mr-sm-2">Password : </label>
        <input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter Password" id="userPwd" name="userPwd">
    </div>

    <!-- Modal footer -->
    <div class="modal-footer">
        <button type="submit" class="btn btn-primary">로그인</button>
        <button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
    </div>
</form>

<MemberController.java>

@RequestMapping(value="login.me") //RequestMapping타입을 어노테이션을 붙여줌으로써 HandlerMapping등록
public void loginMember() {
    System.out.println("로그인요청");
}

** Spring에서 Parameter(요청 시 전달값)를 받는 방법

1) HttpServletRequest를 이용해서 전달받기(기존의 JSP/Servlet 때 방식)
해당 메서드의 매개변수로 HttpServletRequest타입을 작성해두면 스프링 컨테이너가 해당 메서드를 호출 시 자동으로 해당 객체를 생성해서 매개변수로 주입해줌

@RequestMapping("login.me") //value=생략가능, 하지만 value값에 들어가는 것
public String loginMember(HttpServletRequest request) {

    String userId = request.getParameter("id");
    String userPwd = request.getParameter("pwd");

    System.out.println("userId : " + userId);
    System.out.println("userPwd : " + userPwd);



    return "main";
}

2) @RequestParam어노테이션을 이용하는 방법 

   request.getParameter("키")로 밸류를 뽑아오는 역할을 대신해주는 어노테이션
   value속성의 밸류로 jsp에서 작성했던 name속성값을 담으면 알아서 해당 매개변수를 받아올 수 있다. 
   만약, 넘어온 값이 비어있는 형태라면 defaultValue속성으로 기본값을 지정할 수 있다.

@RequestMapping("login.me")
public String loginMember(@RequestParam(value = "id", defaultValue="aaa") String userId, @RequestParam(value="pwd")String userPwd) {

    System.out.println("userId : " + userId);
    System.out.println("userPwd : " + userPwd);
    return "main";

}

3) @RequestParam 어노테이션을 생략하는 방법

단, 매개변수명을 jsp의 name속성값(요청 시 전달하는 값의 키값)과 동일하게 세팅해둬야 자동으로 값이 주입
단점으로는 위에서 사용했던 defaultValue속성은 사용할 수 없음

//같은 클래스 내에서 같은 매핑값을 가진 어노테이션이 존재해서는 안됨
@RequestMapping("login.me")
public String loginMember(String id, String pwd) {

    System.out.println("userId : " + id);
    System.out.println("userPwd : " + pwd);

    Member m = new Member();

    m.setUserId(id);
    m.setUserPwd(pwd);

    //Service쪽 메소드에 m을 전달하면서 조회
    return "main";
}

4) 커멘드 객체 방식

해당 메서드의 매개변수로
요청 시 전달값을 담고자하는 VO클래스의 타입을 세팅 후 
요청 시 전달값의 키값(jsp의 name속성값)을 VO의 필드명으로 작성

스프링 컨테이너가 해당 객체를 기본생성자로 생성 후 내부적으로 setter메소드를 찾아서 
요청 시 전달값을 해당 필드에 담아줌  == setter주입

**반드시 name속성값과 담고자하는 필드명이 동일해야함 +  기본생성자가 꼭 있어야 함 + setter가 꼭 있어야 함

@RequestMapping("login.me")
public String loginMemer(Member m) {

    Member loginUser = memberService.loginMember(m);

    if(loginUser == null) { //로그인 실패 => 에러문구르르 requestScope에 담고 에러페이지로 포워딩
        System.out.println("로그인실패");
    }else {//로그인 성공 => loginUser를 sessionScope에 담고 메인페이지 url로 요천
        System.out.println("로그인성공");
    }


    return "main";
}

- memberService 객체 생성하는 방법

@Autowired //맞는 타입(MemberSerice)을 연결해준다는 뜻
private MemberService memberService; //인터페이스타입으로 선언만 (다형성을 적용할 수 있다는 장점)

 

*    기존 객체 생성 방식
- 객체간의 결합도가 높아짐

(B클래스의 수정이 일어날 경우 B클래스를 의존하고 있던 A클래스도 하나하나 전부 다 바꿔줘야 함)
- 서비스가 동시에 매우 많은 횟수가 요청될 경우 그만큼 객체 생성된다.

@Autowired //맞는 타입(MemberSerice)을 연결해준다는 뜻
private MemberService memberService; //인터페이스타입으로 선언만 (다형성을 적용할 수 있다는 장점)

** 요청 처리 후 응답데이터를 담고 응답페이지로 포워딩 또는 url재요청 하는 방법

1. 스프링에서 제공 Model객체를 사용하는 방법
포워딩할 응답뷰로 전달하고자 하는 데이터를 맵형식(key-value)으로 담을 수 있는 영역
Model객체는 requestScope
단, setAttrubute가 아닌 addAttribute메소드를 호출해야함

@RequestMapping("login.me")
public String loginMember(Member m, Model model, HttpSession session) {

    Member loginUser = memberService.loginMember(m);

    if(loginUser == null) {//로그인 실패 => 에러문구 requestScope에 담아서 에러페이지로 포워딩
        model.addAttribute("errorMsg", "로그인실패");

        return "common/errorPage";

    }else {	//로그인 성공 => loginUser를 sessionScope에 담고 메인페이지 url로 요청

        session.setAttribute("loginUser", loginUser);

        //url재요청 방식 == sendRedirect
        //redirect:요청할 url


        return "redirect:/";
        //localhost:8007/spring   + "/"
    }

 2. 스프링에서 제공하는 ModelAndView객체를 사용하는 방법
 Model은 데이터 key-value세트로 담을 수 있는 공간이라고 한다면
 View는 응답 뷰에 대한 정보를 담을 수 있는 공간 => 단독사용불가!
 
 Handler Adapter가  ModelAndView가 아니면 넘겨줌
 
 Model과 View가 결합된 형태의 객체 
 단, Model객체는 단독사용이 가능하지만 View객체는 ModelAndView타입으로만 사용이 가능하다.

@RequestMapping(value="login.me")
public ModelAndView loginMember(Member m, 
                                ModelAndView mv, 
                                HttpSession session) {

    Member loginUser = memberService.loginMember(m);

    //Member m의 userId 필드 : 사용자가 입력한 아이디
    //Member loginUser의 userId 필드 : 조회된아이디

    //Member m의 userPwd : 사용자가 입력한 비밀번호(평문)
    //Member loginUser의 userPwd필드 : DB에 기억된 암호화된 비밀번호

    //BCryptPasswordEncoder 객체 matches()
    //matchs(평문, 암호문)을 전달
    //암호문에 포함되어있는 Salt값을 판단해서 Salt값을 더한 후 암호화를 진행하여 두 값이 같은지 비교 후 일치한다면 true반환

    if(loginUser != null && bcryptPasswordEncoder.matches(m.getUserPwd(), loginUser.getUserPwd())) {

        session.setAttribute("loginUser", loginUser);
        mv.setViewName("redirect:/");

    }else {//로그인 실패 => 에러문구 requestScope에 담아서 에러페이지로 포워딩
        mv.addObject("errorMsg", "로그인실패");
        mv.setViewName("common/errorPage");
    }
    return mv;
}

<MemberSeviceImpl>

@Autowired
private MemberDao memberDao;

//sqlSessionTemplate객체를 사용해서 bean등록을 했었음
@Autowired
private SqlSessionTemplate sqlSession; //기존의 mybatis의 sqlSession대체

bean으로 등록된 애들 중에 자료형 맞는 애들로 인젭션좀 해줘 & 와이어링

@Override
public Member loginMember(Member m) {

    //SqlSessionTemplate객체를 bean으로 등록 후 @Autowired
    //스프링이 사용 후 자동으로 객체를 알아서 반납시켜주기때문에
    //내가 더이상 close() 호출할 일이없다.

    return memberDao.loginMember(sqlSession, m);
}

<MemberDao>

public Member loginMember(SqlSessionTemplate sqlSession, Member m) {

    return sqlSession.selectOne("memberMapper.loginMember", m);
}

<member-mapper.xml>

<mapper namespace="memberMapper">
<!-- 로그인용 쿼리문 -->
<select id="loginMember" parameterType="member" resultMap="memberResultSet">
    SELECT
             USER_ID
            ,USER_PWD
            ,USER_NAME
            ,EMAIL
            ,GENDER
            ,AGE
            ,PHONE
            ,ADDRESS
            ,ENROLL_DATE
            ,MODIFY_DATE
            ,STATUS
        FROM
            MEMBER
        WHERE
            USER_ID = #{userId}
        AND 
            STATUS = 'Y'
</select>

- resultMap

<resultMap id="memberResultSet" type="member">
    <result column="USER_ID" property="userId"/>
    <result column="USER_PWD" property="userPwd"/>
    <result column="USER_NAME" property="userName"/>
    <result column="EMAIL" property="email"/>
    <result column="GENDER" property="gender"/>
    <result column="AGE" property="age"/>
    <result column="PHONE" property="phone"/>
    <result column="ADDRESS" property="address"/>
    <result column="ENROLL_DATE" property="enrollDate"/>
    <result column="MODIFY_DATE" property="modifyDate"/>
    <result column="STATUS" property="status"/>
</resultMap>

2. 로그아웃

@RequestMapping("logout.me")
public String logoutMember(HttpSession session) {
    session.invalidate();

    return "redirect:/";
}

3. 회원가입

<c:when test="${empty loginUser}">
    <!-- 로그인 전 -->
    <a href="enrollForm.me">회원가입</a>
    <a data-toggle="modal" data-target="#loginModal">로그인</a>
 </c:when>

포워딩을 시켜줌 => 경로를 지정 못하기 때문에 

@RequestMapping("enrollForm.me")
public String enrollForm() {
    //WEB-INF/views/    member/ memberEnrollForm    .jsp  <=로 forwarding
    return "member/memberEnrollForm";
}

회원가입 폼의 매핑값

<form action="insert.me" method="post">

<MemberController>

@RequestMapping("insert.me")
public String insertMember(Member m, Model model) {

사용자가 입력한 값을 Member객체로 받아왔을 때 여러가지 문제점이 발생했음

1. 한글깨짐문제발생 => web.xml에 스프링에서 제공하는 인코딩 필터 등록

 

2. 나이를 입력하지 않았을 경우 int자료형에 빈 문자열이 넘어오기 때문에 자료형이 맞지 않는 문제발생

(400 Bad Request error 발생)

=> Member클래스의 age필드의 int형 => String형으로 변경

반복되는 getter, setter, toString등의 메소드 작성코드를 줄여주는 다이어트 라이브러리 = Lombok

* Lombok 설치방법
1) 라이브러리 다운 후 적용(Maven pon.xml)


2) 다운로드 된 jar파일을 찾아서 설치(작업할 IDE를 체크)

C:\dev\apache-maven-3.8.6\repository\org\projectlombok\lombok\1.18.12 
3) IDE재실행

@NoArgsContructor
@AllArgsConstrutor
@Getter
@Setter
@ToString

* Lombok 사용 시 주의사항
- uName, bTitle 같이 앞글자가 외자인 필드명은 만들지 말 것
즉, 필드명 작성시 최소 소문자로 두 글자 이상으로 시작할 것 
=> EL표기법 사용시 내부적으로 getter메소드를 찾을 때 
getuName(), getbTitle()이라는 이름의 메소드를 호출

lombok의 명명규칙 getName(), getBTitle()이라고 만들어줌

@Setter @Getter @ToString
public class Member {

3. 비밀번호가 사용자가 입력한 있는 그대로의 평문

 Bcrypt방식을 사용할 것

=>  스프링 시큐리티 모듈에서 제공(pom.xml에 라이브러리 추가)

=> BCryptPasswordEncoder 클래스를 xml파일에 bean으로 등록

=> web.xml에 spring-security.xml파일을 로딩할 수 있도록 작성

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    /WEB-INF/spring/spring-security.xml <!--추가-->
    </param-value>
</context-param>

root-context읽은 다음 우리가 만든 security파일 읽도록! 

- bean등록 파일 만드는 방법(원래는 root-context에서 해도 됨)

spring폴더에 new > Spring Bean Configuration File > "파일이름명"

체크하기

<spring-security.xml>

<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bcryptPasswordEncoder"/>

 

<MemberController>

- 암호화 작업(암호문을 만들어내는 과정)

@RequestMapping("insert.me")
	public String insertMember(Member m, Model model) {
        String encPwd = bcryptPasswordEncoder.encode(m.getUserPwd());

        m.setUserPwd(encPwd);//Member객체에 userPwd필드에 평문이 아닌 암호문으로 세팅
        
        int result = memberService.insertMember(m);
		
		if(result>0) { //성공 => 메인페이지 url재요청
			return "redirect:/";
		} else {
			model.addAttribute("errorMsg", "회원가입실패");
			return "common/errorPage";
		}

* 암호문으로 DB에 저장했으므로 로그인 했을 때 암호문과 평문을 매치해 조회해야함

BCryptPasswordEncoder 객체 matches()
- matchs(평문, 암호문)을 전달
=> 암호문에 포함되어있는 Salt값을 판단해서 Salt값을 더한 후 암호화를 진행하여 두 값이 같은지 비교 후 일치한다면 true반환

if(loginUser != null && bcryptPasswordEncoder.matches(m.getUserPwd(), loginUser.getUserPwd())) {

    session.setAttribute("loginUser", loginUser);
    mv.setViewName("redirect:/");

}else {//로그인 실패 => 에러문구 requestScope에 담아서 에러페이지로 포워딩
    mv.addObject("errorMsg", "로그인실패");
    mv.setViewName("common/errorPage");
}
return mv;

<MemberServiseImpl>

@Override
public int insertMember(Member m) {
    //int result = memberDao.insertMember(sqlSession, m);
    //sqlSessionTemplate 객체가 자동 commit해줌

    return memberDao.insertMember(sqlSession, m);
}

<MemberDao>

public int insertMember(SqlSessionTemplate sqlSession, Member m) {

    return sqlSession.insert("memberMapper.insertMember", m);
}

<member-mapper>

<!-- 회원가입용 쿼리문 -->
<insert id="insertMember" parameterType="member">
    INSERT 
        INTO
             MEMBER 
                (				 
                 USER_ID
                ,USER_PWD
                ,USER_NAME
                ,EMAIL
                ,GENDER
                ,AGE
                ,PHONE
                ,ADDRESS
                )
    VALUES (
            #{userId}
            ,#{userPwd}
            ,#{userName}
            ,#{email}
            ,#{gender}
            ,#{age}
            ,#{phone}
            ,#{address}
            )
</insert>