웹 애플리케이션의 규모가 커지고 복잡해짐에 따라, 효과적인 접근 제어 시스템의 필요성이 더욱 중요해지고 있다. 역할 기반 접근 제어(RBAC)는 이러한 요구를 충족시키는 강력한 방법이다. 이 글에서는 React와 TypeScript를 사용하여 클라이언트 사이드 RBAC 시스템을 구현하는 방법을 살펴보자.
RBAC의 필요성
사용자 수 증가와 웹사이트 유료화에 따라, 사용자 권한에 맞는 페이지와 컨텐츠 접근 제어가 필수적이다. RBAC를 통해 무료, 유료(일반, 기업, 계약) 등 다양한 권한 레벨을 구분하고, 맞춤형 서비스를 제공할 수 있다.
1. 사용자 권한 정보의 글로벌 상태 관리
우선, React의 상태 관리 라이브러리인 Recoil을 사용하여 사용자의 권한 정보를 전역 상태로 관리해 보자.
import { atom, useRecoilState, useRecoilValue } from 'recoil';
import { ROLE } from './types';
interface UserState {
isAuthenticated: boolean;
roles: ROLE[];
}
const userState = atom<UserState>({
key: "userState",
default: {
isAuthenticated: false,
roles: [],
},
});
export function useUser() {
const [user, setUser] = useRecoilState(userState);
const setUserRoles = (roles: ROLE[]) => {
const isAuthenticated = roles.length > 0;
setUser({ isAuthenticated, roles });
};
const clearUser = () => {
setUser({ isAuthenticated: false, roles: [] });
};
return { user, setUserRoles, clearUser };
}
export const useAuthorize = (accessKey: string) => {
const { roles } = useRecoilValue(userState);
const permissionList = permissions[accessKey] || [];
return permissionList.some((permission) => roles.includes(permission));
};
useUser
훅은 사용자 정보 설정과 초기화를, useAuthorize
훅은 특정 액세스 키에 대한 권한 검사를 수행한다.
2. 권한 정보 설정 및 중앙 관리
권한 정보를 하나의 객체에서 관리하여 복잡성을 줄이고 유지 보수를 용이하게 할 수 있다.
interface Permissions {
[key: string]: ROLE[];
}
export const permissions: Permissions = {
"page.admin": [ROLE.ADMIN],
"page.tools": [ROLE.ADMIN, ROLE.ENTERPRISE],
"content.items": [ROLE.ADMIN, ROLE.ENTERPRISE, ROLE.GENERAL, ROLE.FREE],
};
이 방식은 새로운 페이지나 기능이 추가될 때마다 해당 접근 권한을 쉽게 정의할 수 있게 한다.
3. HOC 및 조건부 라우팅 컴포넌트
사용자 권한에 따른 접근 제어를 위해 HOC와 조건부 라우팅 컴포넌트를 추가 한다.
export function withRoles<T>(
Component: React.ComponentType<T>,
accessKey: string
) {
return function WithRolesWrapper(props: T) {
const isAuthorized = useAuthorize(accessKey);
if (!isAuthorized) {
console.warn(`Unauthorized access attempt to ${accessKey}`);
return <Unauthorized />;
}
return <Component {...props} />;
};
}
interface PrivateRouteProps extends RouteProps {
component: React.ComponentType<any>;
accessKey: string;
}
export const PrivateRoute: React.FC<PrivateRouteProps> = ({
component: Component,
accessKey,
...rest
}) => {
const isAuthorized = useAuthorize(accessKey);
return (
<Route
{...rest}
render={(props) =>
isAuthorized ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
};
withRoles
HOC는 컴포넌트 레벨에서, PrivateRoute
는 라우팅 레벨에서 접근 제어를 수행한다.
사용 예시
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { useUser } from './hooks';
import { withRoles } from './hoc';
import { PrivateRoute } from './components';
import { ROLE } from './types';
const LoginComponent: React.FC = () => {
const { setUserRoles } = useUser();
const handleLogin = async (username: string, password: string) => {
try {
const roles = await authenticateUser(username, password);
setUserRoles(roles);
} catch (error) {
console.error('Login failed:', error);
}
};
// ...
};
const AdminPage: React.FC = () => {/* ... */}
const AdminPageWrapper = withRoles(AdminPage, "page.admin");
const AppRoutes: React.FC = () => (
<Switch>
<Route path="/login" component={LoginComponent} />
<PrivateRoute path="/admin" component={AdminPageWrapper} accessKey="page.admin" />
{/* ... */}
</Switch>
);
이 예시는 로그인 처리와 관리자 페이지 접근 제어를 보여준다.
보안 고려사항
- 서버 측 검증: 클라이언트 측 RBAC만으로는 불충분하다. 모든 중요한 작업은 반드시 서버 측에서 사용자의 권한을 재검증해야 한다. 이는 클라이언트 측 조작을 통한 무단 접근을 방지하기 위함이다.
- 토큰 관리: JWT와 같은 안전한 토큰 기반 인증 시스템을 사용하고, 적절한 만료 시간을 설정한다. 토큰은 안전하게 저장되어야 하며, HTTPS를 통해서만 전송되어야 한다.
- 최소 권한 원칙: 사용자에게 필요한 최소한의 권한만을 부여한다. 이는 보안 사고 발생 시 피해를 최소화하는 데 도움이 된다.
- 로깅과 모니터링: 모든 중요한 접근 시도와 권한 변경을 로깅하고 주기적으로 검토한다. 이상 행동을 빠르게 탐지하고 대응하는 데 필수적이다.
- 정기적인 보안 감사: RBAC 시스템의 구성과 권한 할당을 정기적으로 검토하고 감사한다. 불필요한 권한이나 오래된 계정을 제거하여 보안 위험을 줄인다.
확장성 고려
- 동적 권한 관리: 관리자 인터페이스를 통한 동적 권한 관리 기능을 추가할 수 있다. 이를 통해 새로운 역할이나 권한을 시스템 재배포 없이 추가하거나 수정할 수 있다.
- 세분화된 권한: 읽기/쓰기/삭제 권한을 별도로 관리하는 등 더 세분화된 권한 체계를 도입할 수 있다. 이는 각 사용자의 책임과 권한을 더 정확하게 반영할 수 있게 해준다.
- 그룹 기반 권한: 개별 사용자뿐만 아니라 사용자 그룹에 대한 권한 관리를 추가할 수 있다. 이는 대규모 조직에서 권한 관리를 더 효율적으로 만들어준다.
- 권한 상속: 역할 간의 계층 구조를 구현하여 권한 상속을 가능하게 할 수 있다. 이를 통해 권한 관리의 복잡성을 줄이고 일관성을 유지할 수 있다.
- 컨텍스트 기반 권한: 시간, 위치, 디바이스 등의 컨텍스트 정보를 고려한 더 복잡한 접근 제어 규칙을 구현할 수 있다.
RBAC 시스템은 앱의 확장성, 보안, 그리고 사용자 관리 효율성을 높여준다. 클라이언트 측 RBAC는 사용자 경험 개선과 서버 부하 감소에 기여하지만, 충분한 보안을 위해서는 반드시 서버 측의 추가적인 검증이 필요하다. 최종적인 보안 결정은 서버와 함께 이루어져야 함을 인지하고 구현해야 한다.