DROP SEQUENCE USER_SEQ;
CREATE SEQUENCE USER_SEQ NOCACHE;
DROP TABLE INACTIVE_USER_T;
DROP TABLE LEAVE_USER_T;
DROP TABLE ACCESS_T;
DROP TABLE USER_T;
--가입한 사용자
CREATE TABLE USER_T (
USER_NO NUMBER NOT NULL, --PK
EMAIL VARCHAR2(100 BYTE) NOT NULL UNIQUE, --이메일을 아이디로 사용
PW VARCHAR2(64 BYTE) NOT NULL, --SHA-256 암호화 방식 사용
NAME VARCHAR2(50 BYTE),
GENDER VARCHAR2(2 BYTE), -- M, F, NO
MOBILE VARCHAR2(15 BYTE), -- 하이픈 제거 후 저장
POSTCODE VARCHAR2( 5BYTE),
ROAD_ADDRESS VARCHAR2(100 BYTE),
JIBUN_ADDRESS VARCHAR2(100 BYTE),
DETAIL_ADDRESS VARCHAR2(100 BYTE),
AGREE NUMBER NOT NULL, --서비스 동의 여부 (0:필수 1: 이벤트)
PW_MODIFIED_AT DATE, --비밀번호
JOINED_AT DATE, -- 가입일
CONSTRAINT PK_USER PRIMARY KEY(USER_NO)
);
-- 접속 기록
CREATE TABLE ACCESS_T (
EMAIL VARCHAR2(100 BYTE) NOT NULL, -- FK 접속한 사용자
LOGIN_AT DATE, -- 로그인 일시
CONSTRAINT FK_USER_ACCESS FOREIGN KEY(EMAIL) REFERENCES USER_T(EMAIL) ON DELETE CASCADE
);
-- 탈퇴한 사용자
CREATE TABLE LEAVE_USER_T (
EMAIL VARCHAR2(50 BYTE) NOT NULL UNIQUE, -- 탈퇴한 사용자 이메일
JOINED_AT DATE, -- 가입일
LEAVED_AT DATE -- 탈퇴일
);
-- 휴면 사용자(1년 이상 접속 기록이 없으면 휴면 처리)
CREATE TABLE INACTIVE_USER_T (
USER_NO NUMBER NOT NULL, --PK
EMAIL VARCHAR2(100 BYTE) NOT NULL UNIQUE, --이메일을 아이디로 사용
PW VARCHAR2(64 BYTE) NOT NULL, --SHA-256 암호화 방식 사용
NAME VARCHAR2(50 BYTE),
GENDER VARCHAR2(2 BYTE), -- M, F, NO
MOBILE VARCHAR2(15 BYTE), -- 하이픈 제거 후 저장
POSTCODE VARCHAR2( 5BYTE),
ROAD_ADDRESS VARCHAR2(100 BYTE),
JIBUN_ADDRESS VARCHAR2(100 BYTE),
DETAIL_ADDRESS VARCHAR2(100 BYTE),
AGREE NUMBER NOT NULL, --서비스 동의 여부 (0:필수 1: 이벤트)
PW_MODIFIED_AT DATE, --비밀번호
JOINED_AT DATE, -- 가입일
INACTIVED_AT DATE, -- 휴면 처리
CONSTRAINT PK_INACTIVE_USER PRIMARY KEY(USER_NO)
);
-- TEST용 INSERT
INSERT INTO USER_T VALUES(USER_SEQ.NEXTVAL, 'user1@naver.com', STANDARD_HASH('1111','SHA256'),'사용자1','M' ,'01011111111', '11111','디지털로','가산동','101동101호',0,TO_DATE('20231001','YYYYMMDD'), TO_DATE('20220101','YYYYMMDD'));
INSERT INTO USER_T VALUES(USER_SEQ.NEXTVAL, 'user2@naver.com', STANDARD_HASH('2222','SHA256'),'사용자2','F' ,'01022222222', '22222','디지털로','가산동','101동101호',0,TO_DATE('20230801','YYYYMMDD'), TO_DATE('20220101','YYYYMMDD'));
INSERT INTO USER_T VALUES(USER_SEQ.NEXTVAL, 'user3@naver.com', STANDARD_HASH('3333','SHA256'),'사용자3','NO' ,'01033333333', '33333','디지털로','가산동','101동101호',0,TO_DATE('20230601','YYYYMMDD'), TO_DATE('20220101','YYYYMMDD'));
INSERT INTO ACCESS_T VALUES('user1@naver.com', TO_DATE('20231018', 'YYYYMMDD')); -- 정상회원
INSERT INTO ACCESS_T VALUES('user2@naver.com', TO_DATE('20220201', 'YYYYMMDD')); -- 휴먼회원
-- 휴면 회원(USER3)
COMMIT;
가입한 사용자가 있는 테이블
사용자의 접속기록이 있는 테이블
탈퇴한 사용자가 있는 테이블
휴면 사용자의 테이블을 만든다.
접속기록과 오늘 날짜를 비교하여서 1년이 되거나 가입을 하고 접속을 하지 않았을 경우 가입일을 기준으로 1년이 지나면 휴면처리를 한다.
접속을 하면 사용자의 접속기록이 등록이 되고 접속기록을 가지고 가입한 사용자의 데이터를 휴면 사용자 테이블로 옮긴 후 가입한 사용자의 데이터를 지운다.
사용자가 이미 휴면 상태이면 휴면 테이블의 데이터를 다시 가입한 사용자의 테이블에 넣고 삭제한다.
이게 관련한 쿼리이다.
-- 1. 로그인 할 때(이메일, 비밀번호 입력)
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;
-- 2. 가입 할때 이메일 중복 체크
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';
-- 3. 휴면 처리할 때 (12 개월 이상 로그인 이력이 없다. 로그인 이력이 전혀 없는 사용자 중에서 가입일이 12개월 이상지났다.
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;
-- 4. 휴면 복원 할 때
INSERT INTO USER_T (
SELECT USER_NO, EMAIL, PW, NAME, GENDER, MOBILE, POSTCODE, ROAD_ADDRESS, JIBUN_ADDRESS, DETAIL_ADDRESS, AGREE, PW_MODIFIED_AT, JOINED_AT
FROM INACTIVE_USER_T
WHERE EMAIL = 'user2@naver.com'
);
DELETE
FROM INACTIVE_USER_T
WHERE EMAIL = 'user2@naver.com';
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='user2@naver.com';
INSERT INTO ACCESS_T VALUES('user2@naver.com', SYSDATE);
COMMIT;
-- 5. 탈퇴 할 때
INSERT INTO LEAVE_USER_T VALUES('user1@naver.com', TO_DATE('20220101','YYYYMMDD'), SYSDATE);
DELETE FROM USER_T WHERE USER_NO = 1;
COMMIT;
로그인을 하지 않은 상태면 로그인과 회원가입을 보여주고 로그인을 한 상태라면 ~님 환영합니다 문구와 로그아웃을 보여준다.
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final MySecurityUtils mySecurityUtils;
@Override
public void login(HttpServletRequest request, HttpServletResponse response) {
String email = request.getParameter("email");
String pw = mySecurityUtils.getSHA256(request.getParameter("pw"));
Map<String, Object> map = Map.of("email", email,
"pw", pw);
UserDto user = userMapper.getUser(map);
if(user != null) {
request.getSession().setAttribute("user", user);
userMapper.insertAccess(email);
try{
response.sendRedirect(request.getContextPath() + "/main.do");
}catch(Exception e) {
e.printStackTrace();
}
} else {
try {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>");
out.println("alert('일치하는 회원 정보가 없습니다.')");
out.println("location.href='" + request.getContextPath() + "/main.do'");
out.println("</script>");
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response) { //세션 날리기
HttpSession session = request.getSession();
session.invalidate(); //세션 초기화
try{
response.sendRedirect(request.getContentType() + "/main.do");
}catch(Exception e) {
e.printStackTrace();
}
}
}
HttpServletRequest을 쓰는 이유는 화면단에서 보내는 모든 정보를 받아오기 위해서이다 .
package com.gdu.myhome.contoller;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.gdu.myhome.service.UserService;
import lombok.RequiredArgsConstructor;
@RequestMapping(value="/user")
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@GetMapping("/login.form")
public String loginForm(HttpServletRequest request, Model model) {
// referer : 이전 주소가 저장되는 요청 헤더 값
String referer = request.getHeader("referer");
model.addAttribute("referer", referer == null ? request.getContextPath() + "/main.do" : referer);
return "user/login";
}
@PostMapping("/login.do")
public void login(HttpServletRequest request, HttpServletResponse response) {
userService.login(request, response);
}
@GetMapping("/logout.do")
public void logout(HttpServletRequest request, HttpServletResponse response) {
userService.logout(request, response);
}
package com.gdu.myhome.util;
import java.security.MessageDigest;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Component;
@Component
public class MySecurityUtils {
/*
* SHA256 암호화
* 1. 입력값을 265비트(32바이트)로 암호화하는 해시 알고리즘이다.
* 2. 원본을 암호화할 수 있으나 암호화된 결과를 원본으로 되돌리는 복호화는 불가능하다.
* 3. java.security 패키지를 활용해서 구하서나, 암호화 디펜던시(예시 common-lang3)를 활용한다.
*
*/
public String getSHA256(String password) {
StringBuilder sb = new StringBuilder();
try {
MessageDigest messageDigest = null;
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(password.getBytes());
byte[] b = messageDigest.digest(); //암호화된 32바이트 배열이 생성됨 -> 64개의 글자가 만들어진다. 1바이트는 8비트 4개씩끊어서 한글자를 만든다.
for(int i = 0; i < b.length; i++) {
sb.append(String.format("%02X", b[i])); // 2자리 16진수 문자열로 변환하기
}
}catch(Exception e) {
e.printStackTrace();
}
return sb.toString();
}
// 인증코드 반환
public String getRandomString(int count, boolean letters, boolean numbers) {
return RandomStringUtils.random(count,letters , numbers);
}
//크로스 사이트 스크립팅(Cross Site Scripting) 방지
public String preventXSS(String source) {
source = source.replace("<","<").replace(">",">");
return source;
}
}
@Mapper
public interface UserMapper {
public UserDto getUser(Map<String, Object> map);
public int insertAccess(String email);
public LeaveUserDto getLeaveUser(Map<String, Object> map);
public int insertUser(UserDto user);
public int insertLeaveUser(UserDto user);
public int deleteUser(UserDto user);
}