[231023] 회원가입 - 주소, 비밀번호 / 마이페이지 - 개인정보 변경, 비밀번호 변경, 인터셉터, 탈퇴

MJ·2023년 10월 28일

수업 TIL🐣💚

목록 보기
68/68

1교시

1. 회원가입 - 주소

  • 다음 주소 api 활용 : 다음 주소 api 접속해서 팝업을 이용하여 도로명 주소와 지번 주소 모두 보여주기 부분 join.jsp로 코드 복사해와서 수정

🔻 join.jsp

  • 우리가 만들어둔 DB에 맞춰서 input 태그들 수정

    빨간 박스가 주소에서 쓸 데이터들. 여기에 맞춰주기
  • input 중에 extraAddress, 안 쓰는데 안 지운 이유: 어차피 name 없으면 파라미터 없어서 안 보내지기 때문에 그냥 뒀다. 같은 이유로 우리가 쓰는 것들은 보내줘야하니까 전부 name 달아줬고
    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
  • 다음에서 만들어서 제공하는 cdn 형식의 스크립트
  • 우리가 쓸 인풋에 id명 일치시켜주고 name 달아준 것 빼면 수정한 것 없이 복붙해온 그대로다. 끝

1. 회원가입 - 비밀번호

정규식 검사

🔻 user_join.js

const fnCheckPassword = () => {
  $('#pw').keyup((ev) => {
    let pw = $(ev.target).val();
    // 비밀번호 : 8~20자, 영문,숫자,특수문자, 2가지 이상 포함
    let validPwCount = /[A-Z]/.test(pw)          // 대문자가 있으면   true
                     + /[a-z]/.test(pw)          // 소문자가 있으면   true
                     + /[0-9]/.test(pw)          // 숫자가 있으면     true
                     + /[^A-Za-z0-9]/.test(pw);  // 특수문자가 있으면 true
    pwPassed = pw.length >= 8 && pw.length <= 20 && validPwCount >= 2;
    if(pwPassed){
      $('#msg_pw').text('사용 가능한 비밀번호입니다.');
    } else {
      $('#msg_pw').text('비밀번호는 8~20자, 영문/숫자/특수문자를 2가지 이상 포함해야 합니다.');       
    }
  })
}
  • keyup : 키보드를 누르는 이벤트
  • $(ev.target).val() : 입력받은 비밀번호 값
  • 정규식 통과 여부는 전역변수 pwPassed에 저장한다. (fnJoin에서 pwPassed 결과 기반 서브밋시킬지 막을지 판별)

비밀번호 확인

  • pw와 pw2가 똑같은지만 확인하면 된다.
const fnCheckPassword2 = () => {
  $('#pw2').blur((ev) => {
    let pw = $('#pw').val();
    let pw2 = ev.target.value;
    pw2Passed = (pw !== '') && (pw === pw2);
    if(pw2Passed){
      $('#msg_pw2').text('');
    } else {
      $('#msg_pw2').text('비밀번호 입력을 확인하세요.');
    }
  })
}
  • keyup을 쓰면 쓰는동안 계속 메시지 떠있기 때문에 거슬려서 blur를 써서 입력이 끝나고 빠져나갈 때 한번만 동작하도록 해줬다. 다르게 입력하고 빠져나갔을 때만 메시지가 뜬다.
  • ev.target.value = 제이쿼리 빼고 써준 거. 빼고 써도 상관없다.
  • $('#msg_pw2').text(''); : pw1과 pw2가 같으면 메시지 지움

2,3교시

2. 회원가입 - 마무리 (서비스, 컨트롤러)

🔻 UserServiceImpl - join메서드

  @Override
  public void join(HttpServletRequest request, HttpServletResponse response) {
    
    String email = request.getParameter("email");
    String pw = mySecurityUtils.getSHA256(request.getParameter("pw"));
    String name = mySecurityUtils.preventXSS(request.getParameter("name"));
    String gender = request.getParameter("gender");
    String mobile = request.getParameter("mobile");
    String postcode = request.getParameter("postcode");
    String roadAddress = request.getParameter("roadAddress");
    String jibunAddress = request.getParameter("jibunAddress");
    String detailAddress = mySecurityUtils.preventXSS(request.getParameter("detailAddress"));
    String event = request.getParameter("event");
    
    UserDto user = UserDto.builder()
                    .email(email)
                    .pw(pw)
                    .name(name)
                    .gender(gender)
                    .mobile(mobile)
                    .postcode(postcode)
                    .roadAddress(roadAddress)
                    .jibunAddress(jibunAddress)
                    .detailAddress(detailAddress)
                    .agree(event.equals("on") ? 1 : 0)
                    .build();
    
    int joinResult = userMapper.insertUser(user);
    
    try {
      
      response.setContentType("text/html; charset=UTF-8");
      PrintWriter out = response.getWriter();
      out.println("<script>");
      if(joinResult == 1) {
        request.getSession().setAttribute("user", userMapper.getUser(Map.of("email", email)));
        userMapper.insertAccess(email);
        out.println("alert('회원 가입되었습니다.')");
        out.println("location.href='" + request.getContextPath() + "/main.do'");
      } else {
        out.println("alert('회원 가입이 실패했습니다.')");
        out.println("history.go(-2)");
      }
      out.println("</script>");
      out.flush();
      out.close();
      
    } catch (Exception e) {
      e.printStackTrace();
    }
    
  }
  • 전달되는 파라미터가 많을 때는 커맨드객체 사용하는게 데이터를 받는데 유리하지만 여기서는 못한다. on 또는 off로 전달되는 agree가 커맨드객체로 써야할 UserDto에서 int로 정의되어 있기 때문에 -> Request를 사용해야 함
  • name은 정규식 검사 없이 50 바이트 초과 금지 조건만 들어가있는 상태(user_join.js에 fnCheckName 참고)라서 악성 스크립트 삽입될 수 있다. -> 크로스 사이트 스크립팅 방지 넣어주기
  • gender는 선택값 정해져 있어서 스크립팅 방지할 필요 없음
  • mobile은 정규식 검사와 공백 제거까지 한 상태라서 스크립팅 방지할 필요 없음
  • 또 값을 못 고치게 막아놓으면 크로스사이트 스크립팅 방지할 필요 없다. postcode, roadAddress, jibunAddress는 못 고치게 막혀있기 때문에 크로스 사이트 스크립팅 방지할 필요 없음
    • join.jsp에서 input 태그 속성으로 readonly 있는게 수정 금지 설정해준 것임
  • event는 hidden 데이터라서 스크립팅 방지할 필요 없음
  • int joinResult = userMapper.insertUser(user) : user 값을 db로 보내기
    • 주의: login 메소드에서 올린 user와 지금 올린 user 값은 다르다.
    • 지금 올린 user = 사용자가 가입하려고 입력한 정보들로만 구성된 user라서 userNo, pwModifiedAt, joinedAt이 누락된 상태
  • if(joinResult == 1) : 회원가입 성공
    • equest.getSession().setAttribute("user", userMapper.getUser(Map.of("email", email))) : 세션에 회원가입 정보가 담겨져 있는 user 올리기. join에서 만들어준 user는 누락이 있기 때문에 getUser로 email이 일치하는 user를 조회해서 가져와주기
    • userMapper.insertAccess(email) : 접속 기록 남기고
    • 메인 페이지로 돌아가기
  • 회원가입 실패하면 두 페이지 뒤, 약관 동의 페이지로 이동
  • insert 작업이 2개이기 때문에 트랜잭션 처리가 필요 -> 클래스 레벨로 UserServiceImpl 전체에 @Transactional 추가 (트랜잭션 처리를 위해 AppConfig에 @EnableTransactionManagement와 TransactionManager 빈 추가는 이미 전프젝 복붙하면서 해준 상태이다)
    • 트랜잭션 처리 기준 : insert, update, delete (db 내용 수정)를 2개 이상 사용
    • int joinResult = userMapper.insertUser(user), userMapper.insertAccess(email) 이렇게 두 개다.
    • 클래스 레벨로 트랜잭션 어노테이션을 추가했기 때문에 트랜잭션할 필요가 없는데 (= db 수정이 일어나지 않는 애들(셀렉트만 하는 애들)) 트랜잭션 어노테이션이 적용되는 경우가 생김 -> 성능이 떨어지기 때문에 @Transactional(readOnly=true)를 따로 명시해준다. (checkEmail 메소드가 여기에 해당)

🔻 UserController

  • 반환타입이 void = 서비스가 컨트롤러 없이 직접 이동하는 것
    • UserServiceImpl join 메소드의 경우 메인 메소드로 직접 이동함

4교시 (마이페이지)

3. 마이페이지

🔻 mypage.jsp

  • 회원가입 시 적은 정보는 sessionScope.user에 저장되어 있으므로 value 값으로 불러오면 됨
  • gender, event : 회원가입 시 체크한 정보로 체크되어 있어야 한다. 회원가입 시 체크한 정보는 sessionScope에 올라가 있으므로 불러와줘서 해당 값에 checked 속성 prop 해주면 됨
  • 개인정보 수정 : 키값을 정해줘야 한다. pk인 userNo가 제일 적당하니까 userNo로 정하고 hidden으로 같이 보내줌 (비밀번호 수정은 별도로)

4. 마이페이지 - 개인정보 수정

🔻 user_modify.js (fnModifyUser)

const fnModifyUser = () => {
  $('#btn_modify').click(() => {
    if(!namePassed){
      alert('이름을 확인하세요.');
      return;
    } else if(!mobilePassed){
      alert('휴대전화번호를 확인하세요.');
      return;
    }
    $.ajax({
      // 요청
      type: 'post',
      url: getContextPath() + '/user/modify.do',
      data: $('#frm_mypage').serialize(),
      // 응답
      dataType: 'json',
      success: (resData) => {  // {"modifyResult": 1}
        if(resData.modifyResult === 1){
          alert('회원 정보가 수정되었습니다.');
        } else {
          alert('회원 정보가 수정되지 않았습니다.');
        }
      }
    })
  })
}
  • $('#frm_mypage').serialize() : 보내려는 데이터가 많을 때 serialize()를 쓰면 입력 요소 중 name이 붙어있는 모든 요소들을 한번에 보낼 수 있다.
  • modifyResult는 서비스, 서비스임플에 구현되어 있다.

5교시

4. 마이페이지 - 개인정보 수정

🔻 UserServiceImpl (modify)

  • 서비스임플에서 전송받은 데이터들 dto로 만들어서 매퍼(updateUser)로 보낸후 매퍼 통해 디비에 update가 되고 반환값은 ajax 응답으로 들어오는 원리 (자세한 설명은 가입에서 했던 것과 같은 원리라서 생략)
if(modifyResult == 1) {}
  • 세션 정보 업데이트 하는 if문

  • 로그인한 사용자의 마이페이지로 업데이트 했다는 것은 로그인한 사용자의 정보가 바뀌었다는 거니까 (매퍼 통해서 db는 수정해줬고) 세션도 따로 수정해줘야 함

    • 세션에 올라갈 때는 모든 타입이 object로 들어가기 때문에 캐스팅이 필요
  • 컨트롤러 작업도 가입에서 했던 것과 같은 원리라서 생략


6,7교시

5. 마이페이지 - 비밀번호 변경


각자 해보기였다.

  • pw.jsp
    비밀번호 본코드 재활용 + 변경 때 추가로 필요한 정보로 누구의 비밀번호를 변경할건지를 알려주기 위해 userNo 보내주는 hidden 타입 input 태그 하나 만들어줌

궁금하면 걍 6교시 초반부 영상을 보자 5분만에 설명끝나서 생략

6. 마이페이지 - 접근 막기

  • 로그인하지 않았을 때는 마이페이지에 접근할 수 없어야 한다. 인터셉터 활용하기

인터셉터

  • 만드는 방법 : HandlerInterceptor라는 인터페이스를 상속해서 만들기
  • preHandle : 요청을 처리하기 이전에 가로채서 처리
    • afterCompletion, postHandle 메소드도 있다. 이 둘은 나중에 처리하는 인터셉터
  • 언제 동작하는지 지정하기 위해 servlet-context.xml에서 어느 인터셉터가 어떤 매핑일 때 동작하는 지 명시

🔻RequiredLoginInterceptor

@Component
public class RequiredLoginInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    HttpSession session = request.getSession();
    
    if(session != null && session.getAttribute("user") == null) {
      response.setContentType("text/html; charset=UTF-8");
      PrintWriter out = response.getWriter();
      out.println("<script>");
      out.println("if(confirm('로그인이 필요한 기능입니다. 로그인할까요?')){");
      out.println("location.href='" + request.getContextPath() + "/user/login.form'");
      out.println("} else {");
      out.println("history.back()");
      out.println("}");
      out.println("</script>");
      out.flush();
      out.close();
      
      return false;  // 가로챈 컨트롤러 요청이 동작하지 않는다.
      
    }
    
    return true;     // 가로챈 컨트롤러 요청이 동작한다.
    
  }
  
}
  • @Component : bean(객체)으로 등록하기 위해 사용
  • if(session != null && session.getAttribute("user") == null) : user가 null이라는 소리는 로그인하지 않았다는 소리

🔻servlet-context.xml

  <interceptors>
  
    <interceptor>
      <mapping path="/user/mypage.form"/>
      <mapping path="/user/modifyPw.form"/>
      <mapping path="/free/write.form"/>
      <beans:bean class="com.gdu.myhome.intercept.RequiredLoginInterceptor" />
    </interceptor>
    
    <interceptor>
      <mapping path="/user/agree.form"/>
      <mapping path="/user/join.form"/>
      <mapping path="/user/login.form"/>
      <beans:bean class="com.gdu.myhome.intercept.ShouldNotLoginInterceptor" />
    </interceptor>
    
  </interceptors>
  • 위에서 만들어준 RequiredLoginInterceptor를 bean으로 불러오고 언제 동작할지 매핑시킬 주소들을 적어줌. 예를 들어 <mapping path="/user/mypage.form"/>은 mypage로 넘어갈 때 동작한다는 뜻

🔻ShouldNotLoginInterceptor

  • 로그인이 되어있을 때 로그인 안되어 있는 상태에서 수행해야 할 페이지에 들어가는 것도 막아준다. (로그인이 되어 있으면 가입이나, 로그인이나, 약관동의 페이지에 들어가지 못함)
  • if(session != null && session.getAttribute("user") != null): 로그인이 되어 있는 상태
  • 인터셉터로 막으면 주소 조작해서 들어가는 것도 다 막아준다. 로그인하면 조인 폼 절대 못감

7교시

6. 마이페이지 - 탈퇴

🔻user_modify.js

const fnLeaveUser = () => {
 $('#btn_leave').click(() => {
   if(confirm('회원 탈퇴하시겠습니까?')){
     $('#frm_mypage').prop('action', getContextPath() + '/user/leave.do');
     $('#frm_mypage').submit();
   }
 })
}
  • 처음 구현 버전

    회원 삭제 시 필요한 키값은 userNo. mypage.jsp에서 hidden으로 입력해줬긴 하지만 js에서 서버로 보내지는 못하고 있다. -> form을 submit 하는 방식으로 바꿔야 한다. 바꿔준게 지금 코드 (폼을 서브밋해서 method도 post로 해줘야 한다. 회원 탈퇴를 get 사용해서 주소로 조작하게 할 수는 없으니까)
    • location, a태그는 get 방식
    • post 요청하는 2가지 = form 서브밋할 때 메소드로 post 지정, ajax 타입을 post로 지정

🔻UserServiceImpl

  • mypage 열어놓고 한참 방치하면 세션 정보가 풀려서 로그인이 풀리는 경우가 발생할 수 있음. 이렇게 로그인이 안 된 상태로 탈퇴 버튼을 누르면 userNo가 오지 않아서 NumberFormatException 발생
    Optional<String> opt = Optional.ofNullable(request.getParameter("userNo"));
    int userNo = Integer.parseInt(opt.orElse("0"));

이렇게 optional 처리를 해주면 null 값에도 대비해줄 수 있다. userNo가 null 값일 경우 0을 보내라는 뜻. 회원번호 0은 없는 사용자이기 때문에 if(insertLeaveUserResult == 1 && deleteUserResult == 1) 에 통과되지 않는다 (원래는 그 밑에 else가 수행되어야 하지만 if문 위에서 null 값에 대한 if문을 추가로 만들어줬기 때문에 else 문이 수행될 일은 거의 없다)

(나머지는 특별히 설명 필요 없어서 과정 안 적음. 순서대로 따라가면서 공부하면 된다,)

0개의 댓글