다음 글은 프론트엔드 유저 권한 정책 유연하게 관리하기을 읽고 고민해본 것에 대한 내용을 담고 있습니다.
여기서 그냥 사용한다는 것은 단순히 페이지에서 useRecoilValue
로 전역상태를 불러와서 아래와 같이 사용한다는 것을 뜻한다. 왜냐, 어차피 기능이 동작하는 것은 똑같기 때문에 이런 생각이 들 수 있다.
{canAccess사장실 && <사장실출입버튼 />}
{퇴사 && <사장실출입버튼 />}
{userRole === "사장" && <사장실출입버튼 />}
{!userRole === "사장" && <퇴사버튼 />}
이런 경우를 생각해보자, 프로젝트가 점점 커지면서 점점 할 작업들이 많아지고 있던 중, 다음과 같은 요구사항을 만나게 된다.
"사장님"이라는 권한을 새로 만들어서 기존의 "관리자"가 사용할 수 있는 기능은 모두 사용하고 추가로 "사장님"만 사용할 수 있는 버튼이 생길 예정입니다.
만약, 각 페이지마다 권한을 확인하도록 했다면 모든 페이지를 돌며 아래와 같이 코드를 수정하는 작업을 해야할 것이다.
{userRole === "관리자" && userRole ==="사장님" && <관리자버튼 />}
반면, 중앙에서 권한을 관리했다면, 아래 코드 하나만 수정하면 끝날 것이다.
get canAccess사장실() {
return this.eligible(['사장', '관리자']);
}
두번째 경우. 프론트와 백엔드 모두 JS를 사용할 때나 프로젝트들을 모노레포로 관리하고 있을 경우, 권한 관리 자체를 해당 파일에서 모두 처리하고 각 프로젝트마다 일관성 있게 권한을 관리할 수 있을 것이다. 이로 인해 코드의 재사용성을 높이고 중복을 크게 제거할 수 있을 것으로 기대된다.
리액트의 ErrorBoundary
에서 공통적인 오류(권한이 null
인 경우)에 대해서도 일괄적으로 처리할 수 있도록 해줄 수 있음은 물론이다. (이로 인해 오류에 대한 처리를 ErrorBoundary
로 넘겨 로직의 분리를 더욱 세분화해줄 수 있다.
객체지향형으로 만든 코드는 아래와 같고,
type RoleType = '사장' | '매니저' | '알바';
class PermissionPolicyChecker {
private static instance: PermissionPolicyChecker;
// 싱글톤 패턴 적용
static getInstance(role: RoleType) {
if (!PermissionPolicyChecker.instance) {
PermissionPolicyChecker.instance = new PermissionPolicyChecker(role);
}
return PermissionPolicyChecker.instance;
}
/**
* 권한은 크게 두 종류로 나눌 수 있습니다.
* 1. 해당 권한인 경우에만 가능한 것
* 2. 해당 권한인 경우에만 불가능한 것
*
* 이를 위해 메서드를 두 가지로 분리했습니다.
*/
private readonly _role: RoleType;
constructor(role: RoleType) {
this._role = role;
}
// 이 권한을 가진 경우만 가능합니다.
private eligible = (roles: RoleType[]) => {
return roles.includes(this._role);
};
// 이 권한을 가진 경우만 불가능합니다.
private ineligible = (roles: RoleType[]) => {
return !roles.includes(this._role);
};
// 사장실 출입하기
get canAccess사장실() {
return this.eligible(['사장']);
}
// 퇴사하기
get 퇴사() {
return this.ineligible(['사장']);
}
}
export default PermissionPolicyChecker;
위 코드를 함수형으로 바꾼 코드는 아래와 같다.
type RoleType = '사장' | '매니저' | '알바';
const eligible = (role: RoleType, roles: RoleType[]) => roles.includes(role);
const ineligible = (role: RoleType, roles: RoleType[]) => !roles.includes(role);
const createPermissionChecker = (role: RoleType) => ({
// 사장실 출입 가능 여부 확인
canAccess사장실: () => eligible(role, ['사장']),
// 퇴사 가능 여부 확인
퇴사: () => ineligible(role, ['사장'])
});
export default createPermissionChecker;