refactor: enhance session management and routing in middleware and API authentication

This commit is contained in:
yoosangwook 2025-05-07 17:16:26 +09:00
parent 5048fe1c08
commit 05f4fe4e94
12 changed files with 194 additions and 52 deletions

View File

@ -12,10 +12,10 @@ const nextConfig: NextConfig = {
source: '/api/user/login', source: '/api/user/login',
destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`, destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`,
}, },
// { {
// source: '/api/:path*', source: '/api/:path*',
// destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,
// }, },
] ]
}, },
} }

View File

@ -1,5 +1,5 @@
import Header from '@/components/ui/common/Header' import Header from '@/components/ui/common/Header'
export default function page() { export default async function page() {
return <Header name={'調査物件一覧'} /> return <Header name={'調査物件一覧'} />
} }

View File

@ -0,0 +1,15 @@
import { sessionOptions } from '@/libs/session'
import { SessionData } from '@/types/Auth'
import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
session.destroy()
// return redirect('/login')
return NextResponse.json({ code: 200, message: 'Logout successful' })
}

View File

@ -1,6 +1,12 @@
import { axiosInstance } from '@/libs/axios' import { cookies } from 'next/headers'
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import { getIronSession } from 'iron-session'
import { axiosInstance } from '@/libs/axios'
import { sessionOptions } from '@/libs/session'
import type { SessionData } from '@/types/Auth'
export async function POST(request: Request) { export async function POST(request: Request) {
const { loginId, pwd } = await request.json() const { loginId, pwd } = await request.json()
@ -10,5 +16,43 @@ export async function POST(request: Request) {
}) })
console.log('🚀 ~ result ~ result:', result) console.log('🚀 ~ result ~ result:', result)
return NextResponse.json('ok') if (result.data.result.code === 200) {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('start session edit!')
session.langCd = result.data.data.langCd
session.currPage = result.data.data.currPage
session.rowCount = result.data.data.rowCount
session.startRow = result.data.data.startRow
session.endRow = result.data.data.endRow
session.compCd = result.data.data.compCd
session.agencyStoreId = result.data.data.agencyStoreId
session.storeId = result.data.data.storeId
session.userId = result.data.data.userId
session.category = result.data.data.category
session.userNm = result.data.data.userNm
session.userNmKana = result.data.data.userNmKana
session.telNo = result.data.data.telNo
session.fax = result.data.data.fax
session.email = result.data.data.email
session.lastEditUser = result.data.data.lastEditUser
session.storeGubun = result.data.data.storeGubun
session.pwCurr = result.data.data.pwCurr
session.pwdInitYn = result.data.data.pwdInitYn
session.apprStatCd = result.data.data.apprStatCd
session.loginFailCnt = result.data.data.loginFailCnt
session.loginFailMinYn = result.data.data.loginFailMinYn
session.priceViewStatCd = result.data.data.priceViewStatCd
session.groupId = result.data.data.groupId
session.storeLvl = result.data.data.storeLvl
session.custCd = result.data.data.custCd
session.builderNo = result.data.data.builderNo
session.isLoggedIn = true
console.log('end session edit!')
await session.save()
}
return NextResponse.json({ code: 200, message: 'Login is Succecss!!' })
} }

View File

@ -1,6 +1,14 @@
import Main from '@/components/ui/Main' import Main from '@/components/ui/Main'
import { sessionOptions } from '@/libs/session'
import type { SessionData } from '@/types/Auth'
import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers'
export default async function Home() { export default async function Home() {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('🚀 ~ Home ~ session:', session)
return ( return (
<> <>
<div className="container"> <div className="container">

View File

@ -1,9 +1,9 @@
'use client' 'use client'
import { axiosInstance } from '@/libs/axios' import { useReducer, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useEffect, useReducer, useState } from 'react' import { axiosInstance } from '@/libs/axios'
import { AxiosResponse } from 'axios'
interface AccountState { interface AccountState {
loginId: string loginId: string
@ -11,6 +11,7 @@ interface AccountState {
} }
export default function Login() { export default function Login() {
const router = useRouter()
//비밀번호 보이기 숨기기 //비밀번호 보이기 숨기기
const [pwShow, setPwShow] = useState(false) const [pwShow, setPwShow] = useState(false)
//ID 저장 체크박스 //ID 저장 체크박스
@ -26,31 +27,31 @@ export default function Login() {
pwd: '', pwd: '',
}) })
interface LoginData {
code: number
message: string | null
}
const { const {
data: loginData, data: loginData,
isPending, isPending,
error, error,
} = useQuery<AxiosResponse, Error>({ } = useQuery<LoginData, Error>({
queryKey: ['login', 'account'], queryKey: ['login', 'account'],
queryFn: () => { queryFn: async () => {
const result = axiosInstance('').post(`/api/auth`, { const { data } = await axiosInstance('').post<LoginData>(`/api/auth`, {
loginId: account.loginId, loginId: account.loginId,
pwd: account.pwd, pwd: account.pwd,
}) })
return result router.push('/')
return data
}, },
enabled: isLogin, enabled: isLogin,
staleTime: 0, staleTime: 0,
retry: false, retry: false,
}) })
useEffect(() => {
if (loginData) {
console.log('🚀 ~ Login ~ loginData:', loginData)
setIsLogin(false)
}
}, [loginData])
return ( return (
<> <>
<div className="login-form-wrap"> <div className="login-form-wrap">

View File

@ -1,16 +1,27 @@
'use client' 'use client'
import { useHeaderStore } from '@/store/header' import { useHeaderStore } from '@/store/header'
import { useRouter } from 'next/navigation' import { useSideNavState } from '@/store/sideNavState'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
export default function Main() { export default function Main() {
const router = useRouter() const router = useRouter()
const pathname = usePathname()
const { setBackBtn } = useHeaderStore() const { setBackBtn } = useHeaderStore()
const { reset } = useSideNavState()
/**
*
*
*/
useEffect(() => { useEffect(() => {
if (pathname === '/') {
setBackBtn(false) setBackBtn(false)
}, []) }
//사이드바 초기화
reset()
}, [pathname])
return ( return (
<> <>

View File

@ -1,16 +1,17 @@
'use client' 'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { Swiper, SwiperSlide } from 'swiper/react' import { Swiper, SwiperSlide } from 'swiper/react'
import { useSideNavState } from '@/store/sideNavState'
import { useHeaderStore } from '@/store/header'
import type { HeaderProps } from '@/types/Header' import type { HeaderProps } from '@/types/Header'
import 'swiper/css' import 'swiper/css'
import { useSideNavState } from '@/store/sideNavState' import { axiosInstance } from '@/libs/axios'
// type HeaderProps = { // type HeaderProps = {
// name: string //header 이름 // name: string //header 이름
@ -20,27 +21,26 @@ import { useSideNavState } from '@/store/sideNavState'
export default function Header({ name }: HeaderProps) { export default function Header({ name }: HeaderProps) {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const { sideNavIsOpen, setSideNavIsOpen, reset } = useSideNavState() const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState()
const [isShowBackBtn, setIsShowBackBtn] = useState<boolean>(false) const { backBtn } = useHeaderStore()
if (pathname === '/login') { if (pathname === '/login') {
return null return null
} }
useEffect(() => { const handleLogout = async () => {
if (pathname !== '/') { const { data } = await axiosInstance(null).get('/api/auth/logout')
setIsShowBackBtn(true) if (data.code === 200) {
router.push('/login')
}
} }
//사이드바 초기화
reset()
}, [pathname])
return ( return (
<> <>
<div className="header-warp"> <div className="header-warp">
<header> <header>
<div className="header-inner"> <div className="header-inner">
{isShowBackBtn && ( {backBtn && (
<div className="back-button-wrap"> <div className="back-button-wrap">
<button className="back-button" onClick={() => router.back()}></button> <button className="back-button" onClick={() => router.back()}></button>
</div> </div>
@ -112,7 +112,9 @@ export default function Header({ name }: HeaderProps) {
<div className="side-nav-footer"> <div className="side-nav-footer">
<ul className="side-footer-list"> <ul className="side-footer-list">
<li className="side-footer-item"> <li className="side-footer-item">
<button className="bold">LOGOUT</button> <button className="bold" onClick={handleLogout}>
LOGOUT
</button>
</li> </li>
<li className="side-footer-item"> <li className="side-footer-item">
<button>Jynoadmin</button> <button>Jynoadmin</button>

View File

@ -1,7 +1,8 @@
import axios from 'axios' import axios from 'axios'
export const axiosInstance = (url: string) => { export const axiosInstance = (url: string | null | undefined) => {
let baseURL = url !== '' || url === undefined ? url : process.env.NEXT_PUBLIC_API_URL const baseURL = url || process.env.NEXT_PUBLIC_API_URL
return axios.create({ return axios.create({
baseURL, baseURL,
headers: { headers: {
@ -22,7 +23,7 @@ axios.interceptors.request.use(
) )
// Response interceptor // Response interceptor
axiosInstance.interceptors.response.use( axios.interceptors.response.use(
(response) => transferResponse(response), (response) => transferResponse(response),
(error) => { (error) => {
// 에러 처리 로직 // 에러 처리 로직

View File

@ -1,5 +1,7 @@
import type { SessionData } from '@/types/Auth'
import { getIronSession, SessionOptions } from 'iron-session' import { getIronSession, SessionOptions } from 'iron-session'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export const sessionOptions: SessionOptions = { export const sessionOptions: SessionOptions = {
cookieName: 'onsitesurvey_session', cookieName: 'onsitesurvey_session',
@ -14,15 +16,34 @@ export const sessionOptions: SessionOptions = {
}, },
} }
export interface SessionData {
username: string | null
email: string | null
isLoggedIn: boolean
}
export const defaultSession: SessionData = { export const defaultSession: SessionData = {
username: '', langCd: null,
email: '', currPage: 0,
rowCount: 0,
startRow: 0,
endRow: 0,
compCd: null,
agencyStoreId: null,
storeId: null,
userId: null,
category: null,
userNm: null,
userNmKana: null,
telNo: null,
fax: null,
email: null,
lastEditUser: null,
storeGubun: null,
pwCurr: null,
pwdInitYn: null,
apprStatCd: null,
loginFailCnt: null,
loginFailMinYn: null,
priceViewStatCd: null,
groupId: null,
storeLvl: null,
custCd: null,
builderNo: null,
isLoggedIn: false, isLoggedIn: false,
} }
@ -31,3 +52,11 @@ export const getSession = async () => {
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
return session return session
} }
export const logout = async () => {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
session.destroy()
return redirect('/login')
}

View File

@ -2,16 +2,17 @@ import { NextResponse } from 'next/server'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
import { getIronSession } from 'iron-session' import { getIronSession } from 'iron-session'
import { SessionData, sessionOptions } from './libs/session' import { sessionOptions } from './libs/session'
import type { SessionData } from './types/Auth'
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
const cookieStore = await cookies() const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
// todo: 로그인 기능 추가 시 주석 해제 // todo: 로그인 기능 추가 시 주석 해제
// if (!session.isLoggedIn) { if (!session.isLoggedIn) {
// return NextResponse.redirect(new URL('/login', request.url)) return NextResponse.redirect(new URL('/login', request.url))
// } }
return NextResponse.next() return NextResponse.next()
} }

30
src/types/Auth.ts Normal file
View File

@ -0,0 +1,30 @@
export interface SessionData {
langCd: null
currPage: Number | null
rowCount: Number | null
startRow: Number | null
endRow: Number | null
compCd: null
agencyStoreId: null
storeId: null
userId: null
category: null
userNm: null
userNmKana: null
telNo: null
fax: null
email: null
lastEditUser: null
storeGubun: null
pwCurr: null
pwdInitYn: null
apprStatCd: null
loginFailCnt: Number | null
loginFailMinYn: null
priceViewStatCd: null
groupId: null
storeLvl: null
custCd: null
builderNo: null
isLoggedIn: boolean
}