[231019] <15> DB, 쿼리 테스트, 암호화 코드, VIEW, 정상적 로그인 처리, 로그아웃

MJ·2023년 10월 25일

수업 TIL🐣💚

목록 보기
66/68

1,2교시

1. DB

USER_T

  • 아이디를 이메일로 쓸 예정이기 때문에 not null, unique 속성 부여해줌

ACCESS_T

  • EMAIL에 PK 안 준 이유
    • pk를 주게 되면 한 사람의 접속시간으로 하나밖에 입력하지 못하게 됨
    • pk를 주는 대신 USER_T의 EMAIL을 참조하는 외래키를 줘야한다 (USER_T의 EMAIL은 PK는 아니지만 UNIQUE기 때문에 참조될 수 있다)
  • 외래키가 있을 때는 참조 무결성 위배 여부를 체크해줘야 한다. 만약 한 사용자가 탈퇴한다면 USER_T의 email 정보도 사라지게 되고 그럼 그 email을 참조하고 있는 ACCESS_T의 email은 참조 무결성이 위배되게 된다.
  • 해결책은 외래키도 함께 삭제(ON DELETE CASCADE)해주거나 NULL처리(ON DELETE SET NULL)해주는 것인데 ACCESS_T의 EMAIL은 NOT NULL이라 선택지는 지우는 것 뿐이다.

LEAVE_USER_T

  • 탈퇴 테이블은 딱히 다른 테이블과 상관없다. 특히 USER_T와 외래키 관계라고 착각하는 경우가 있는데 가입한 사용자와 탈퇴한 사용자가 동시에 테이블에 존재할 수 없다. (USER_T에서 LEAVE_USER_T로 옮겨가는 것일 뿐)

테스트용 INSERT

  • STANDARD_HASH('1111','SHA256') : 오라클 내장 암호화함수, 비밀번호와 암호화 방식 인수로 넣어주면 됨

쿼리 테스트 로그인

SELECT USER_NO, EMAIL, PW, NAME, GENDER, MOBILE, POSTCODE, ROAD_ADDRESS, JIBUN_ADDRESS, DETAIL_ADDRESS, AGREE, PW_MODIFIED_AT, JOINED_AT
  FROM USER_T
 WHERE EMAIL = 'user1@naver.com'
   AND PW = '0FFE1ABD1A08215353C233D6E009613E95EEC4253832A761AF28FF37AC5A150C';

INSERT INTO ACCESS_T VALUES('user1@naver.com', SYSDATE);
COMMIT;   
  • 로그인 판별법: 이메일과 비밀번호가 db 데이터와 일치하는지 판단
  • 비밀번호는 1111이 아닌 암호화된 데이터와 일치하는지를 판단해야한다. 코드에 나온 암호문은 SELECT STANDARD_HASH('1111','SHA256') FROM DUAL의 결과물
  • SELECT로 결과가 조회되면 회원이라는 뜻이니까 user1을 ACCESS_T에 추가 (=접속된 것)
  • 휴면 회원에 대한 테스트도 따로 만들어준다. (가서 보기)

쿼리 테스트 이메일 중복 체크

SELECT EMAIL
  FROM USER_T
 WHERE EMAIL = 'user4@naver.com';

SELECT EMAIL
  FROM LEAVE_USER_T
 WHERE EMAIL = 'user4@naver.com';

SELECT EMAIL
  FROM INACTIVE_USER_T
 WHERE EMAIL = 'user4@naver.com';
  • 탈퇴한 사용자의 이메일도 쓸 수 없다 하기로 했으므로 LEAVE_USER_T도 체크해준다
  • 세 값이 모두 NULL이어야 중복 없다는 뜻 (user4는 중복 없으니까 사용 가능하다)

쿼리 테스트 탈퇴

  • 탈퇴 삽입 후 회원 삭제
  • LEAVE_USER_T 삽입 후 USER_T에서 회원 삭제
  • INSERT 후 DELETE 하므로 반드시 트랜잭션 처리가 필요. (INSERT, DELETE, UPDATE 쿼리문이 두 개 이상 교합이 되면 해당 서비스는 반드시 트랜잭션 -> 컨트롤러단에서 트랜잭션하겠다고 하면 자동으로 된다..)

쿼리 테스트 휴면 처리

INSERT INTO INACTIVE_USER_T
(
SELECT USER_NO, U.EMAIL, PW, NAME, GENDER, MOBILE, POSTCODE, ROAD_ADDRESS, JIBUN_ADDRESS, DETAIL_ADDRESS, AGREE, PW_MODIFIED_AT, JOINED_AT, SYSDATE
  FROM USER_T U LEFT OUTER JOIN ACCESS_T A
    ON U.EMAIL = A.EMAIL
 WHERE MONTHS_BETWEEN(SYSDATE, LOGIN_AT) >= 12
    OR (LOGIN_AT IS NULL AND MONTHS_BETWEEN(SYSDATE, JOINED_AT) >= 12)
);

DELETE
  FROM USER_T
 WHERE EMAIL IN(SELECT U.EMAIL
                  FROM USER_T U LEFT OUTER JOIN ACCESS_T A
                    ON U.EMAIL = A.EMAIL
                 WHERE MONTHS_BETWEEN(SYSDATE, LOGIN_AT) >= 12
                    OR (LOGIN_AT IS NULL AND MONTHS_BETWEEN(SYSDATE, JOINED_AT) >= 12));
COMMIT;
  • 가입일 조건이 필요한 이유 = 한번도 로그인하지 않아서 아예 로그인 이력이 없는 사용자는 ACCESS_T에 기록이 없기 때문에 12개월 이상 조건만으로는 카운팅이 안된다.
  • select로 가져온 결과를 insert할 때는 values 없다.
  • INACTIVED_AT은 SELECT로 가져오는게 아닌 SYSDAYE 처리
  • 언제 로그인 했는지 따지려면 ACCESS_T의 LOGIN_AT이 필요. JOIN해준다. ACCESS_T에 없더라도 USER_T의 모든 정보가 조회되어야 하므로 LEFT OUTER JOIN

3교시

  • Myhome = spring 최종버전 프로젝트
  • 3교시엔 프로젝트 세팅만

4교시

2. Myhome - MySecurityUtils (암호화 코드)

MySecurityUtils.java

SHA256 암호화

  1. 입력값을 256비트(32바이트)로 암호화하는 해시 알고리즘이다.
  2. 원본을 암호화할 수 있으나, 암호화된 결과를 원본으로 되돌리는 복호화는 불가능하다.
  3. java.security 패키지를 활용해서 구하거나, 암호화 디펜던시(예시 commons-lang3)를 활용할 수 있다.

코드

  • 32바이트는 64글자 -> PW의 DB 사이즈를 64로 한 이유
  • %02X : %X=16진수(암호화 결과가 대문자로 나오므로 대문자로 써야한다.), %02X=두자리 16진수로 만들고 자리가 부족하면 0 넣어달라는 뜻


이렇게 비밀번호가 로그에 찍히더라도 암호화시켜줬기 때문에 보이지 않게 되었다. (이 결과는 7교시 마지막에 돌려본거..)

  • 참고. 암호문은 0 -> 0000 F -> FFFF
  • 한자리당 2씩 4개니까 2의 4제곱 -> 16진수

5,6교시

3. Myhome - view

init.css

css 초기화 값들 저장해놓은 곳

header.jsp

  • init.css 등 css들을 읽고 가져오는 작업을 한다. (캐싱)
  • 캐싱문제 만약 init.css를 수정한다면? => init.css는 변경됐는데 이를 캐싱해온 header.jsp는 변경되지 않는 현상이 발생할 수도 있다. 이를 방지하기 위해 타임스탬프 값을 담아둔 dt를 활용한다. dt를 넣어주면 링크가 실행할 때마다 계속 캐싱된다.
    • 개발이 종료되면 css가 수정될 일도 없어지고 캐싱문제가 발생할 일이 없어지기 때문에 성능 향상을 위해 dt 지운다. 대신 그 자리에 ver=1.0같은 버전을 명시해준다.
  • gnb_wrap : 메뉴
  • <div class="main_wrap"> : header.jsp가 main.jsp의 시작부에 불러와지므로 header의 끝부분에 이 코드를 넣어주고 footer에서 div 태그를 닫아주면 main.jsp(포함 페이지 전체)에 main.css가 적용됨, footer에도 마찬가지로 적용.

main.jsp

  • <jsp:inclube>는 파라미터 전달이 필요할 때 쓰고 <%@ include>(include 지시어)는 파라미터 전달이 필요하지 않을 때 사용

header 가져오기

<jsp:include page="header.jsp"> 
// main.jsp와 같은 폴더에 있으므로 경로에 파일 이름만 적으면 된다.
  <jsp:param value="마이홈" name="title"/>
</jsp:include>
  • jsp:param의 value는 header.jsp의 title로 들어간다.
이부분 (header.jsp 가서 확인)
<title>${param.title == null ? '마이홈' : param.title}</title>
푸터는 파라미터 전달 필요없어서 지시어를 사용했다.
<%@ include file="footer.jsp" %>

7교시

4. Myhome - 정상적인 로그인 처리

userMapper.xml

  • sqldeveloper에서 쿼리 테스트했던 내용들 기반으로 적었음.
  • insertAccess: 이메일 받아올꺼니까 파라미터타입 String

UserMapper.java

  • userMapper.xml의 parameterType이 UserMapper.java의 매개변수
  • userMapper.xml의 resultType이 UserMapper.java의 반환타입
userMapper.xml의 <select id="getUser" parameterType="Map" resultType="UserDto"> 
UserMapper 인터페이스의 public UserDto getUser(Map<String, Object> map);

UserService.java

  • login: request로 받는 두가지 이유 1. 암호화를 서비스에서 구현할건데 중간에 꺼내면 로그에 찍혀버리니까 그러지 않기 위해서 2. response 통해서 세션 쓰기 위해 (이메일+비밀번호+원하는 세션)

UserServiceImpl.java

login

  • user 가 null이 아니면 로그인 성공, user가 null이면 로그인 실패
String pw = mySecurityUtils.getSHA256(request.getParameter("pw"));
  • mySecurityUtils에 작성해줬던 암호화 메소드 getSHA256를 통해 getParameter로 얻어온 pw(비밀번호) 값을 암호화해준다.
Map<String, Object> map = Map.of("email", email
                                   , "pw", pw);
  • mapper로 보낼 map

if(user != null)

UserDto user = userMapper.getUser(map);

request.getSession().setAttribute("user", user);
  • request.getSession() 여기까지 하면 세션, 세션에 올리는 건 setAttribute. 세션에 사용자 정보(map으로 지정해준거)를 담고 있는 user를 통째로 올림. 로그인되었다면 세션에 user가 있다. 아니라면 세션에 user가 없다 (계속 기억하고 있어야할 정보)
userMapper.insertAccess(email);
  • userMapper인터페이스 통해서 userMapper.xml에 적어둔 insertAccess작업 즉 email 전달받아서 ACCESS_T에 데이터 올리는 작업.

  • 다했으니까 main으로 돌아가기(추후 코드 바뀔 예정인데 일단은 돌아가보자). insert, update, delete를 했으면 그 다음은 redirect다. (login 메소드 포함 insert, update, delete는 반환타입이 없음(void) -> 반환타입이 없으면 controller를 통한 이동이 아님 -> controller 통한 이동이면 컨트롤러가 redirect를 하든 forward 하든 결정할 수 있는데 그게 아니니까 리다이렉트만 가능)
    • response 객체 받아왔으니까 컨트롤러 통하지 않고 서비스가 직접 응답해도 된다. response가 직접 응답하니까 컨트롤러한테 주는게 없고 그 이유로 void 반환타입이 나옴. (void면 response(서비스가 직접 응답) 거의 세트라고 보면 됨) 주로 응답이 여러가지인 경우 서비스가 직접 응답함.
  • main 찾아가는 법: request를 통해 contextPath 얻을 수 있다.
  • response로 리다렉할때 try-catch문 안 써주니 오류나서 써준건데 나중엔 다 지우고 throws 해줄 예정

else

  • user 없음 -> 로그인 실패
  • 일치하는 회원정보가 없다는 alert 메시지

UserController.java

  @PostMapping("/login.do")
  public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {
    userService.login(request, response);
  }
  • login은 이미 서비스가 반환을 끝냈으므로 컨트롤러가 딱히 뭔가 할 게 없음.
  • userService의 login 메소드로 request랑 response 전달
  • 데이터 바디에 정보 실어 보내는 postmapping 사용햇네,,

header.jsp login_wrap

  • 이제 정상적 로그인은 다 구현했으니 화면에 보여주는 작업해부자
  • sessionScope : 세션에 있다고 알려주는 el 내장 객체, 세션에 있는 유저 -> sessionScope.user
  • sessionScope.user !=null = user가 세션에 있다 = 로그인 되어있다
  • UserServiceImpl에서 로그인되면 user를 세션에 올려줬었다. 로그인 = 세션에 user가 있음

5. Myhome - 로그아웃

  • logout을 할 때도 session 필요, session에 올라간 user를 지워줘야 하니까
  • 자동 로그인은 로그인 정보가 쿠키에 들어있어야 함. 로그아웃할 때는 이 쿠키도 제거해줘야 함. (+ 쿠키 사용을 위해서는 response가 필요 -> 결론은 UserService에서 login하고 똑같이 request랑 response 전달해야함)

UserServiceImpl.java

  @Override
  public void logout(HttpServletRequest request, HttpServletResponse response) {
    
    HttpSession session = request.getSession();
    
    session.invalidate();
    
    try {
      response.sendRedirect(request.getContextPath() + "/main.do");
    } catch (Exception e) {
      e.printStackTrace();
    }
    
  }
  • session.invalidate(); : 세션 초기화
  • 세션 초기화(로그아웃) 후 메인 페이지로 이동.
  • 로그아웃했을 때는 db 변화가 없으므로 dao(mapper)와 연결은 없음

UserController.java

  • a링크로 로그아웃했으므로 GetMapping. 주소에 실어보내는 겟매핑,,

0개의 댓글