icon안동민 개발노트

사용자 역할 기반 접근 제어


 사용자 역할 기반 접근 제어(RBAC)는 애플리케이션의 보안을 강화하고 사용자별로 다른 기능을 제공하는 데 중요한 역할을 합니다.

 이 절에서는 Next.js 애플리케이션에서 RBAC를 구현하는 방법을 살펴보겠습니다.

사용자 역할 정의 및 저장

 먼저, 사용자 역할을 정의하고 저장하는 방법을 알아보겠습니다.

 일반적으로 사용자 역할은 데이터베이스에 저장되며, NextAuth.js의 세션에 포함시켜 사용할 수 있습니다.

  1. 데이터베이스 스키마 예시
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(255),
  email VARCHAR(255),
  role ENUM('admin', 'user', 'guest') DEFAULT 'user'
);
  1. NextAuth.js 설정에서 역할 정보 포함
app/api/auth/[...nextauth]/route.js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
 
const handler = NextAuth({
  providers: [
    // 프로바이더 설정...
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.role = user.role;
      }
      return token;
    },
    async session({ session, token }) {
      session.user.role = token.role;
      return session;
    },
  },
});
 
export { handler as GET, handler as POST };

서버 사이드에서의 역할 기반 접근 제어

 서버 사이드에서 역할 기반 접근 제어를 구현하는 방법을 알아보겠습니다.

  1. 미들웨어를 사용한 라우트 보호
middleware.js
import { NextResponse } from 'next/server'
import { getToken } from 'next-auth/jwt'
 
export async function middleware(req) {
  const token = await getToken({ req, secret: process.env.JWT_SECRET })
 
  if (req.nextUrl.pathname.startsWith('/admin')) {
    if (token?.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', req.url))
    }
  }
 
  return NextResponse.next()
}
  1. API 라우트에서의 역할 확인
app/api/admin-only/route.js
import { getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]/route"
 
export async function GET(req) {
  const session = await getServerSession(authOptions)
 
  if (!session || session.user.role !== 'admin') {
    return new Response("Unauthorized", { status: 401 })
  }
 
  // 관리자용 로직
  return new Response("Admin only content", { status: 200 })
}

CSR에서의 역할 기반 접근 제어

 클라이언트 사이드에서도 역할에 따른 UI 렌더링과 접근 제어를 구현할 수 있습니다.

  1. 훅을 사용한 역할 확인
hooks/useRole.js
import { useSession } from 'next-auth/react'
 
export function useRole() {
  const { data: session } = useSession()
  return session?.user?.role
}
  1. 컴포넌트 수준의 조건부 렌더링
import { useRole } from '../hooks/useRole'
 
function AdminOnlyComponent() {
  const role = useRole()
 
  if (role !== 'admin') {
    return null
  }
 
  return <div>관리자용 컨텐츠</div>
}
  1. 고차 컴포넌트(HOC)를 사용한 역할 기반 접근 제어
function withRoleAccess(WrappedComponent, allowedRoles) {
  return function WithRoleAccess(props) {
    const role = useRole()
 
    if (!allowedRoles.includes(role)) {
      return <div>접근 권한이 없습니다.</div>
    }
 
    return <WrappedComponent {...props} />
  }
}
 
const AdminPage = withRoleAccess(AdminDashboard, ['admin'])

API 라우트와의 연관성

 11장에서 다룰 API 라우트와 역할 기반 접근 제어는 밀접하게 연관됩니다

 API 라우트에서 사용자의 역할을 확인하여 특정 작업의 수행 여부를 결정할 수 있습니다.

 예를 들어, 관리자만 사용자 정보를 수정할 수 있는 API 엔드포인트를 만들 수 있습니다.

app/api/users/[id]/route.js
import { getServerSession } from "next-auth/next"
import { authOptions } from "../../auth/[...nextauth]/route"
 
export async function PUT(req, { params }) {
  const session = await getServerSession(authOptions)
 
  if (!session || session.user.role !== 'admin') {
    return new Response("Unauthorized", { status: 401 })
  }
 
  // 사용자 정보 수정 로직
  // ...
 
  return new Response("User updated", { status: 200 })
}

실습 : 역할별 대시보드 구현

 관리자, 일반 사용자, 게스트 역할에 따라 다른 기능을 제공하는 대시보드 페이지를 구현해보겠습니다.

  1. 대시보드 컴포넌트 생성
app/dashboard/page.js
'use client'
 
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
 
export default function Dashboard() {
  const { data: session, status } = useSession()
  const router = useRouter()
 
  if (status === 'loading') {
    return <div>Loading...</div>
  }
 
  if (!session) {
    router.push('/login')
    return null
  }
 
  const role = session.user.role
 
  return (
    <div>
      <h1>Dashboard</h1>
      {role === 'admin' && <AdminDashboard />}
      {role === 'user' && <UserDashboard />}
      {role === 'guest' && <GuestDashboard />}
    </div>
  )
}
 
function AdminDashboard() {
  return (
    <div>
      <h2>Admin Dashboard</h2>
      <ul>
        <li>사용자 관리</li>
        <li>시스템 설정</li>
        <li>로그 분석</li>
      </ul>
    </div>
  )
}
 
function UserDashboard() {
  return (
    <div>
      <h2>User Dashboard</h2>
      <ul>
        <li>프로필 수정</li>
        <li>내 게시물</li>
        <li>메시지 확인</li>
      </ul>
    </div>
  )
}
 
function GuestDashboard() {
  return (
    <div>
      <h2>Guest Dashboard</h2>
      <p>제한된 기능만 사용 가능합니다. 더 많은 기능을 이용하려면 회원가입을 해주세요.</p>
      <ul>
        <li>공개 게시물 보기</li>
        <li>회원가입</li>
      </ul>
    </div>
  )
}
  1. 네비게이션 컴포넌트에 역할별 메뉴 추가
components/Navigation.js
'use client'
 
import Link from 'next/link'
import { useSession } from 'next-auth/react'
 
export default function Navigation() {
  const { data: session } = useSession()
 
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/dashboard">Dashboard</Link>
      {session?.user.role === 'admin' && <Link href="/admin">Admin</Link>}
      {session?.user.role === 'user' && <Link href="/profile">Profile</Link>}
      {!session && <Link href="/login">Login</Link>}
    </nav>
  )
}

 이 실습을 통해 사용자 역할에 따라 다른 대시보드 내용과 네비게이션 메뉴를 제공하는 방법을 익힐 수 있습니다.

 이는 실제 애플리케이션에서 자주 사용되는 패턴으로, 사용자별로 맞춤화된 경험을 제공하는 데 도움이 됩니다.

 Next.js에서의 사용자 역할 기반 접근 제어 구현은 애플리케이션의 보안을 강화하고 사용자 경험을 개선하는 중요한 요소입니다.

 서버 사이드와 클라이언트 사이드에서 적절히 역할을 확인하고 접근을 제어함으로써, 안전하고 효율적인 애플리케이션을 구축할 수 있습니다.