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 암호화
- 입력값을 256비트(32바이트)로 암호화하는 해시 알고리즘이다.
- 원본을 암호화할 수 있으나, 암호화된 결과를 원본으로 되돌리는 복호화는 불가능하다.
- 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 초기화 값들 저장해놓은 곳
- 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 지시어)는 파라미터 전달이 필요하지 않을 때 사용
<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);
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 사용햇네,,
- 이제 정상적 로그인은 다 구현했으니 화면에 보여주는 작업해부자
- 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. 주소에 실어보내는 겟매핑,,