Skip to content

Commit

Permalink
[Feat] 액세스토큰 만료시간 관련 자동 로그아웃 기능 구현
Browse files Browse the repository at this point in the history
- 액세스토큰 유효시간 만료 관련하여 자동 로그아웃 기능구현 (현재 Refresh 토큰 기반의 자동 로그인이 구현되지 않은 상황이므로 이를 보완하여 클라이언트 단에서 자동 로그아웃이 이루어지도록 구현)
- 기존에 로컬 스토리지에 저장하던 액세스 토큰을 세션 스토리지에 저장하는 것으로 수정하여 클라이언트 파일 내 전반적인 수정이 이루어짐
- 이외 변경사항 : 불필요한 통신 발생하지 않도록 조건 분기 (특정 조건일 경우에만 필요한 정보 -> 마운트 시 자동적으로 fetching 하지 않도록 설정)

Issues #122
  • Loading branch information
novice1993 committed Sep 20, 2023
1 parent 812e9cc commit d0bc9d3
Show file tree
Hide file tree
Showing 24 changed files with 211 additions and 108 deletions.
6 changes: 3 additions & 3 deletions client/src/components/Headers/LoginHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ const LoginHeader: React.FC<LoginHeaderProps> = () => {

// 로고 클릭 처리 함수
const handleLogoClick = () => {
window.location.reload()
window.location.reload();
};

// 로그아웃 클릭 처리 함수
const handleLogout = () => {
dispatch(setLogoutState()); // 전역변수에서 로그아웃 상태로 설정
localStorage.removeItem("accessToken"); // 엑세스 토큰 제거
localStorage.removeItem("refreshToken"); // 리프레시 토큰 제거
sessionStorage.removeItem("accessToken"); // 엑세스 토큰 제거
sessionStorage.removeItem("refreshToken"); // 리프레시 토큰 제거

// 페이지를 새로고침합니다.
window.location.reload();
Expand Down
68 changes: 58 additions & 10 deletions client/src/components/Logins/EmailLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import axios from "axios";
import styled from "styled-components";
import React, { useState } from "react";
import { toast } from "react-toastify";
import { setLoginState } from "../../reducer/member/loginSlice";
import { setLogoutState } from "../../reducer/member/loginSlice";
import { useDispatch } from "react-redux";

const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onSignup }) => {
const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onSignup }) => {
const titleText = "이메일로 로그인";
const emailLabelText = "이메일";
const passwordLabelText = "비밀번호";
Expand Down Expand Up @@ -32,8 +34,7 @@ const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onS
};

const handleEnterPress = (event: React.KeyboardEvent<HTMLInputElement>, target?: "password" | "loginButton") => {

if (event.key === 'Enter') {
if (event.key === "Enter") {
if (target === "password") {
(document.querySelector('input[type="password"]') as HTMLInputElement).focus();
} else if (target === "loginButton") {
Expand All @@ -42,6 +43,7 @@ const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onS
}
};

// 🔴 자옹 로그아웃 테스트
const handleLoginClick = async () => {
try {
const response = await axios.post("http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members/login", { email, password }, { validateStatus: (status) => status >= 200 && status < 600 });
Expand All @@ -51,10 +53,56 @@ const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onS
const refreshToken = response.headers["refresh"];

dispatch(setLoginState());
if (accessToken) localStorage.setItem("accessToken", accessToken);
if (refreshToken) localStorage.setItem("refreshToken", refreshToken);

onLogin();
if (accessToken) sessionStorage.setItem("accessToken", accessToken);
if (refreshToken) sessionStorage.setItem("refreshToken", refreshToken);

const toastStyle = {
fontSize: "15px",
fontWeight: 350,
color: "black",
};

// 로그인 유지시긴 알림
toast.warning("로그인 상태는 30분 동안 지속됩니다", {
style: toastStyle,
position: "top-center",
});

// 로그아웃 알림 1차 설정 + 이때 시간 저장
const settingTime01 = 1000 * 7; // 10초
const settingTime02 = 1000 * 7; // 10초
const logoutAlarmTime01 = Date.now(); // 소환한 시간
sessionStorage.setItem("logoutAlarmTime01", `${logoutAlarmTime01}`); // 세션 스토리지에 저장

setTimeout(() => {
// 첫번째 알람 실행되었으므로 -> 첫번째 시간기록 삭제
sessionStorage.removeItem("logoutAlarmTime01");

toast.warning("1분 뒤 로그아웃 처리됩니다", {
style: toastStyle,
position: "top-center",
});

// 2차 알람 및 로그아웃 처리 + 토큰 삭제
const logoutAlarmTime02 = Date.now();
sessionStorage.setItem("logoutAlarmTime02", `${logoutAlarmTime02}`);

setTimeout(() => {
// 두번째 알람 실행되었으므로 -> 두번째 시간기록 삭제
sessionStorage.removeItem("logoutAlarmTime02");

dispatch(setLogoutState());
sessionStorage.removeItem("accessToken");
sessionStorage.removeItem("refreshToken");

toast.warning("로그아웃 처리되었습니다", {
style: toastStyle,
position: "top-center",
});
}, settingTime02);
}, settingTime01);

// onLogin();
onClose();
} else {
setGeneralError(response.data.message || JSON.stringify(response.data));
Expand All @@ -74,14 +122,14 @@ const EmailLoginModal: React.FC<EmailLoginModalProps> = ({ onClose, onLogin, onS
<CloseButton onClick={onClose}>&times;</CloseButton>
<Title>{titleText}</Title>
<Label>{emailLabelText}</Label>
<Input type="email" placeholder="이메일을 입력하세요" value={email} onChange={handleEmailChange} onKeyDown={(event) => handleEnterPress(event, "password")}/>
<Input type="email" placeholder="이메일을 입력하세요" value={email} onChange={handleEmailChange} onKeyDown={(event) => handleEnterPress(event, "password")} />
<Label>{passwordLabelText}</Label>
<Input type="password" placeholder="비밀번호를 입력하세요" value={password} onChange={handlePasswordChange} onKeyDown={(event) => handleEnterPress(event, "loginButton")}/>
<Input type="password" placeholder="비밀번호를 입력하세요" value={password} onChange={handlePasswordChange} onKeyDown={(event) => handleEnterPress(event, "loginButton")} />
{generalError && <ErrorMessage>{generalError}</ErrorMessage>}
<RightAlignedButton onClick={handleFindPasswordClick}>{findPasswordText}</RightAlignedButton>
<LoginButton onClick={handleLoginClick}>{loginButtonText}</LoginButton>
<BottomText>
{noAccountText} <RegisterButton onClick={onSignup}>{registerButtonText}</RegisterButton>
{noAccountText} <RegisterButton onClick={onSignup}>{registerButtonText}</RegisterButton>
</BottomText>
</ModalContainer>
</ModalBackground>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ const WaitOrderIndicator = () => {
};

stompClient.connect(headers, () => {
console.log("Connected with headers:", headers);
console.log("Connected to the WebSocket server");
// console.log("Connected with headers:", headers);
// console.log("Connected to the WebSocket server");

stompClient.subscribe(`/sub/${memberId}`, async (data) => {
const responseData = JSON.parse(data.body);
Expand Down
43 changes: 12 additions & 31 deletions client/src/components/communityComponents/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ const Comments = ({ boardId }: { boardId: number }) => {

const fetchCommentsFromServer = async () => {
try {
const response = await axios.get(
`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/api/boards/${boardId}`
);
const response = await axios.get(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/api/boards/${boardId}`);

// 게시판 데이터에서 댓글 부분을 추출합니다.
const comments = response.data.comments || [];
Expand All @@ -31,7 +29,7 @@ const Comments = ({ boardId }: { boardId: number }) => {
setCommentsValue(e.target.value);
};

const accessToken = localStorage.getItem("accessToken");
const accessToken = sessionStorage.getItem("accessToken");

const handleClickSubmit = async () => {
if (commentsValue) {
Expand All @@ -44,15 +42,11 @@ const Comments = ({ boardId }: { boardId: number }) => {
};

try {
const response = await axios.post(
`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/api/boards/${boardId}/comments`,
newCommentData,
{
headers: {
Authorization: accessToken,
},
}
);
const response = await axios.post(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/api/boards/${boardId}/comments`, newCommentData, {
headers: {
Authorization: accessToken,
},
});
if (response.status === 201) {
setCommentsValue("");
fetchCommentsFromServer();
Expand Down Expand Up @@ -111,11 +105,8 @@ const Comments = ({ boardId }: { boardId: number }) => {
const currentTime: Date = new Date();
const createdAtTime: Date = new Date(createdAt);

const timeDifferenceInMilliseconds: number =
currentTime.getTime() - createdAtTime.getTime();
const timeDifferenceInSeconds = Math.floor(
timeDifferenceInMilliseconds / 1000
);
const timeDifferenceInMilliseconds: number = currentTime.getTime() - createdAtTime.getTime();
const timeDifferenceInSeconds = Math.floor(timeDifferenceInMilliseconds / 1000);

if (timeDifferenceInSeconds < 60) {
return "방금 전";
Expand All @@ -134,14 +125,8 @@ const Comments = ({ boardId }: { boardId: number }) => {
return (
<CommentContainer>
<div>
<CommentInput
type="text"
value={commentsValue}
onChange={handleOnChange}
/>
<CommentInputSubmit onClick={handleClickSubmit}>
{CommentText.write}
</CommentInputSubmit>
<CommentInput type="text" value={commentsValue} onChange={handleOnChange} />
<CommentInputSubmit onClick={handleClickSubmit}>{CommentText.write}</CommentInputSubmit>
<CommentCount onClick={handleShowMoreComments}>
{CommentText.replyCount} {commentData.length}
{CommentText.replyText}
Expand All @@ -157,11 +142,7 @@ const Comments = ({ boardId }: { boardId: number }) => {
</CommentsDiv>
<CommentsDiv key={el.id}>
<CommentTextDiv>{el.content}</CommentTextDiv>
<CommentDeleteButton
onClick={() => handleDeleteComment(el.commentId)}
>
{CommentText.delete}
</CommentDeleteButton>
<CommentDeleteButton onClick={() => handleDeleteComment(el.commentId)}>{CommentText.delete}</CommentDeleteButton>
</CommentsDiv>
</CommentArea>
))}
Expand Down
5 changes: 2 additions & 3 deletions client/src/components/communityComponents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ const TimeLineComponent = () => {
};

// 서브밋 버튼 클릭
const accessToken = localStorage.getItem("accessToken");
const accessToken = sessionStorage.getItem("accessToken");
const handleClickSubmit = async () => {

// 로그인 토큰 확인
// 로그인 토큰 확인
if (!accessToken) {
toast.error("로그인이 필요한 서비스입니다", {
autoClose: 1000,
Expand Down
19 changes: 7 additions & 12 deletions client/src/components/watchlist/WatchList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import StockItem from "./StockItem.tsx";
import useCompanyData from "../../hooks/useCompanyData";
import useGetStars from "../../hooks/stars/useGetstars.ts"; // useGetStars 훅의 경로를 지정해주세요.
import { useSelector } from "react-redux";
import { RootState } from "../../store/config.ts";
import LoginRequestIndicator from "../HoldingList/LoginRequestIndicator.tsx"
import { RootState } from "../../store/config.ts";
import LoginRequestIndicator from "../HoldingList/LoginRequestIndicator.tsx";

const WatchList: React.FC<WatchListProps> = ({ currentListType, onChangeListType, openOAuthModal}) => {
const WatchList: React.FC<WatchListProps> = ({ currentListType, onChangeListType, openOAuthModal }) => {
const [isMenuOpen, setMenuOpen] = useState(false);
const loginStatus = useSelector((state: RootState) => state.login);

Expand All @@ -20,21 +20,16 @@ const WatchList: React.FC<WatchListProps> = ({ currentListType, onChangeListType

const [starredCompanyIds, setStarredCompanyIds] = useState<number[]>([]);


useEffect(() => {
if (starredData) {

setStarredCompanyIds(starredData.map(item => item.companyResponseDto.companyId));
setStarredCompanyIds(starredData.map((item) => item.companyResponseDto.companyId));
}
}, [starredData]);

useEffect(() => {

}, [starredCompanyIds]);
useEffect(() => {}, [starredCompanyIds]);

const handleCompanyDelete = (deletedCompanyId: number) => {

setStarredCompanyIds(prevState => prevState.filter(id => id !== deletedCompanyId));
setStarredCompanyIds((prevState) => prevState.filter((id) => id !== deletedCompanyId));
};
return (
<WatchListContainer>
Expand Down Expand Up @@ -64,7 +59,7 @@ const WatchList: React.FC<WatchListProps> = ({ currentListType, onChangeListType
type WatchListProps = {
currentListType: "전체종목" | "관심종목" | "보유종목";
onChangeListType: (type: "전체종목" | "관심종목" | "보유종목") => void;
openOAuthModal: () => void; // Add this line
openOAuthModal: () => void; // Add this line
};

const WatchListContainer = styled.div`
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/stars/useDeletestars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMutation } from 'react-query';

// DELETE 요청을 수행하는 함수
const deleteStarData = async (companyId: number) => {
const accessToken = localStorage.getItem('accessToken');
const accessToken = sessionStorage.getItem('accessToken');
const response = await fetch(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stars/?companyId=${companyId}`, {
method: 'DELETE',
headers: {
Expand Down
34 changes: 20 additions & 14 deletions client/src/hooks/stars/useGetstars.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery } from 'react-query';
import { useQuery } from "react-query";

type StockAsBiResponseDto = {
stockAsBiId: number;
Expand Down Expand Up @@ -34,24 +34,30 @@ type StarDataItem = {

type StarData = StarDataItem[];

const fetchStarData = async (): Promise<StarData> => {
const accessToken = localStorage.getItem('accessToken');
const res = await fetch('http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stars', {
headers: {
'Authorization': `${accessToken}`
// : Promise<StarData>

const fetchStarData = async () => {
const accessToken = sessionStorage.getItem("accessToken");

// 로그인 상태에만 관심목록 데이터 호출하도록 설정
if (accessToken !== null) {
const res = await fetch("http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stars", {
headers: {
Authorization: `${accessToken}`,
},
});

if (!res.ok) {
const data = await res.json();
throw new Error(data.message || "Something went wrong");
}
});

if (!res.ok) {
const data = await res.json();
throw new Error(data.message || 'Something went wrong');
}

return res.json();
return res.json();
}
};

const useGetStar = () => {
return useQuery<StarData, Error>('starData', fetchStarData);
return useQuery<StarData, Error>("starData", fetchStarData);
};

export default useGetStar;
2 changes: 1 addition & 1 deletion client/src/hooks/stars/usePoststars.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMutation } from 'react-query';

const postStarData = async (companyId: number) => {
const accessToken = localStorage.getItem('accessToken');
const accessToken = sessionStorage.getItem('accessToken');
const res = await fetch(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stars/?companyId=${companyId}`, {
method: 'POST',
headers: {
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/useCash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { RootState } from '../store/config';
const BASE_URL = 'http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080';

const getAuthHeader = () => {
const accessToken = localStorage.getItem('accessToken');
const accessToken = sessionStorage.getItem('accessToken');
return {
'Authorization': `${accessToken}`
};
Expand Down
4 changes: 2 additions & 2 deletions client/src/hooks/useDeleteMembers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function useDeleteMember() {
const dispatch = useDispatch();

return useMutation(async () => {
const accessToken = localStorage.getItem('accessToken');
const accessToken = sessionStorage.getItem('accessToken');
const response = await axios.delete(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members`, {
headers: {
Authorization: `${accessToken}`
Expand All @@ -30,7 +30,7 @@ export function useDeleteMember() {
}, {
onSuccess: () => {
// 토큰 삭제
localStorage.removeItem('accessToken');
sessionStorage.removeItem('accessToken');

// 로그아웃 상태로 변경
dispatch(setLogoutState());
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/useDeleteStockOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default useDeleteStockOrder;
const deleteStockOrder = async (orderId: number, cancleVolume: number) => {
const url = `http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stock/stockorders?stockOrderId=${orderId}&stockCount=${cancleVolume}`;

const accessToken = localStorage.getItem("accessToken");
const accessToken = sessionStorage.getItem("accessToken");
const options = {
headers: {
Authorization: `${accessToken}`,
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/useGetCash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const useGetCash = () => {
export default useGetCash;

const getCashData = async () => {
const accessToken = localStorage.getItem("accessToken");
const accessToken = sessionStorage.getItem("accessToken");
const options = {
headers: {
Authorization: `${accessToken}`,
Expand Down
Loading

0 comments on commit d0bc9d3

Please sign in to comment.