[ 목차 ]
728x90
반응형
0. React 애플리케이션의 권한 관리 시스템 흐름도
- 초기 접근 프로세스
- 사용자 접근
- 토큰 확인
- 토큰 유효성 검사
- 권한 검증
- 기능별 권한 검증 프로세스
- CRUD 작업별 권한 확인
- 권한에 따른 UI 요소 표시/숨김
- 기능 실행 전 권한 재확인
- 에러 처리 프로세스
- 권한 없음 처리
- 토큰 만료 처리
- 리프레시 실패 처리
1. 소개
안녕하세요! 오늘은 프로덕션 레벨의 React 애플리케이션에서 권한 관리 시스템을 어떻게 구현하는지, 상세한 코드와 주석을 통해 알아보겠습니다.
2. 권한 관리의 핵심 구조
2.1 withAuth HOC (Higher-Order Component)
/**
* 권한 검증을 위한 Higher-Order Component
*@paramgssp - getServerSideProps 함수
*@paramrequiredPermission - 필요한 권한 문자열
*@returns GetServerSideProps 함수
*/
const withAuth = (
gssp: (context: GetServerSidePropsContext, store: Store) => Promise<{ props: any }>,
requiredPermission?: string
): GetServerSideProps => {
return wrapper.getServerSideProps((store) => async (context: GetServerSidePropsContext) => {
// 요청에서 토큰 추출
const { req, res, resolvedUrl } = context;
const accessToken = req.cookies['accessToken'];
const refreshToken = req.cookies['refreshToken'];
// 메인 페이지이고 토큰이 없는 경우 바로 통과
if (resolvedUrl === '/' && !accessToken && !refreshToken) {
return await gssp(context, store);
}
// 권한 검증 로직
if (requiredPermission) {
const state = store.getState();
const userPermissions = state.login.loginPermission?.permissions || [];
// 필요한 권한이 없는 경우 403 에러 반환
if (!userPermissions.includes(requiredPermission)) {
context.res.statusCode = 403;
return {
props: {
statusCode: 403,
message: "접근 권한이 없습니다."
}
};
}
}
// 나머지 로직 처리...
});
}
2.2 토큰 디코딩 기능
/**
* JWT 토큰을 디코딩하여 권한 정보 추출
*@paramtoken - JWT 토큰 문자열
*@returns TokenData 객체 (id, roles, permissions)
*/
export function decodeTokenInfo(token: string): TokenData {
try {
// 토큰을 세 부분으로 분리
const [header, payload, signature] = token.split('.');
// Base64Url 디코딩
const decodedPayload = base64UrlDecode(payload);
// DEFLATE 압축 해제
const inflated = pako.inflate(
new Uint8Array(decodedPayload.split('').map(c => c.charCodeAt(0))),
{ to: 'string' }
);
// JSON 파싱
const decodedToken = JSON.parse(inflated);
// 권한 정보 추출
const allClaims = decodedToken.roles || [];
return {
id: decodedToken.sub,
// ROLE_ 로 시작하는 권한만 필터링
roles: allClaims.filter(claim => claim.startsWith('ROLE_')),
// PERM_ 로 시작하는 권한에서 접두사 제거
permissions: allClaims.filter(claim => claim.startsWith('PERM_'))
.map(perm => perm.replace('PERM_', ''))
};
} catch (error) {
console.error('Token decoding failed:', error);
return { id: '', roles: [], permissions: [] };
}
}
3. 실제 구현 사례: 스팸 관리 시스템
3.1 메인 컴포넌트 구현
/**
* 스팸 관리 시스템 메인 컴포넌트
* 권한에 따른 기능 제어 구현
*/
const Spam = () => {
// Redux store에서 권한 정보 가져오기
const permissions = useTypeSelector((state) => state.login.loginPermission.permissions);
/**
* 스팸 추가 기능 핸들러
* CREATE 권한 확인 후 처리
*/
const handleAddClick = () => {
if(!permissions.includes(SPAM_MANAGEMENT_CREATE)){
router.push('/spam/spam');
return;
}
setIsAddModalOpen(true);
};
/**
* 스팸 수정 기능 핸들러
* UPDATE 권한 확인 후 처리
*/
const handleEditClick = (row) => {
if(!permissions.includes(SPAM_MANAGEMENT_UPDATE)){
router.push('/spam/spam');
return;
}
setEditingSpam(row);
setNewValue('');
setIsEditModalOpen(true);
};
/**
* 스팸 삭제 기능 핸들러
* DELETE 권한 확인 후 처리
*/
const handleDelete = () => {
if(!permissions.includes(SPAM_MANAGEMENT_DELETE)){
router.push('/spam/spam');
return;
}
if (selectedRows.length === 0) {
openAlert('알림', '삭제할 항목을 선택해주세요.');
return;
}
openConfirm('확인', '선택한 항목을 삭제하시겠습니까?', () => {
// 선택된 항목 삭제 처리
selectedRows.forEach(row => {
const content = row.type === 'GT' ? row.text :
(row.type === 'CT' ? row.text : row.text);
dispatch(deleteSpamRequest({
spamType: 'spam',
id: row.id,
cpid: row.id,
type: row.type,
content
}));
});
});
};
}
3.2 테이블 컬럼 설정
/**
* 동적 테이블 컬럼 설정
* 권한에 따른 컬럼 표시 제어
*/
const columns = [
{
header: '스팸',
accessor: 'spamCount',
width: '17%',
// 스팸 타입에 따른 텍스트 렌더링
render: (value: string, row: any) => {
switch(row.type) {
case 'GT': return row.text;// 전체 스팸
case 'CT': return row.text;// 카테고리 스팸
default: return value;
}
}
},
{
header: '카테고리',
accessor: 'id',
width: '13%',
// 카테고리 ID 렌더링 로직
render: (value: string) => value === ' ' ? '(ALL)' : value
},
// ... 기타 컬럼
{
header: '변경',
accessor: 'edit',
width: '12%',
// 수정 권한이 있는 경우에만 버튼 표시
render: (value: string, row: any) =>
permissions.includes(SPAM_MANAGEMENT_UPDATE) ?
<EditButton row={row} onEditClick={handleEditClick} /> :
null
}
];
3.3 API 통신 및 에러 처리
/**
* 스팸 추가 API 호출 함수
*@paramspamData - 추가할 스팸 데이터
*/
const handleAddSpam = async (spamData) => {
try {
// API 호출
const response = await api.post('/spam/spam/add', null, {
params: {
type: spamData.type,
cpid: spamData.cpid,
content: spamData.content
},
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
// 성공 시 처리
if (response.status === 200) {
dispatch(fetchSpamsRequest({
spamType: 'spam',
params: {types:['GT','CT'], page: 0, size: 40}
}));
handleCloseAddModal();
openAlert('성공', '스팸이 성공적으로 추가되었습니다.');
}
} catch (error) {
// 에러 처리
console.error('스팸 추가 중 오류 발생:', error);
let errorMessage = '스팸 추가 중 오류가 발생했습니다.';
// 중복 데이터 에러 처리
if (error.response?.data?.includes('ORA-00001')) {
errorMessage = '이미 존재하는 스팸 데이터입니다.';
}
setAddError(errorMessage);
openAlert('오류', errorMessage);
}
};
4. 보안 관련 구현
4.1 토큰 관리
/**
* 토큰 저장 및 보안 설정
* HttpOnly, Secure, SameSite 설정으로 보안 강화
*/
res.setHeader('Set-Cookie', [
`accessToken=${token}; HttpOnly; Path=/; Secure; SameSite=Strict`,
`refreshToken=${refreshToken}; HttpOnly; Path=/; Secure; SameSite=Strict`
]);
/**
* 토큰 만료 시 리프레시 처리
*/
if (accessToken && refreshToken) {
try {
// 리프레시 토큰으로 새 액세스 토큰 발급
store.dispatch(refreshTokenRequest());
// 사용자 정보 다시 로드
store.dispatch(loadMyInfoRequest());
// ... 추가 처리
} catch (error) {
console.error('토큰 리프레시 실패:', error);
}
}
5. 결론
이러한 방식의 권한 관리 시스템은 다음과 같은 장점을 제공합니다:
- 체계적인 권한 검증: HOC를 통한 일관된 권한 검증
- 세분화된 접근 제어: 기능별 세밀한 권한 관리
- 보안성: HttpOnly 쿠키, 토큰 리프레시 등 보안 기능 구현
- 사용자 경험: 적절한 에러 처리와 피드백
- 유지보수성: 모듈화된 구조로 유지보수 용이
이러한 구조는 특히 엔터프라이즈 환경에서 요구되는 복잡한 권한 관리를 효과적으로 구현할 수 있게 해줍니다. 각 코드 블록에 대한 자세한 주석을 통해 시스템의 작동 방식을 더 쉽게 이해하고 수정할 수 있습니다.
반응형
'Next.js' 카테고리의 다른 글
Next.js의 라우팅 시스템 이란 (0) | 2024.11.12 |
---|---|
Next.js의 렌더링 전략: SSR과 CSR, SSG (Hydration, Pre-rendering, Dynamic Routes, Build Time, ISR ) (4) | 2024.11.11 |
React의 고차 컴포넌트(HOC) (0) | 2024.11.10 |
React 기본 용어 및 개념 (Component, Props, State, 랜더링....) 핵심 흐름 (3) | 2024.11.09 |
[Next.js] TOAST UI POPUP(팝업 창) 활용 2편 (0) | 2024.11.04 |