본문 바로가기
WIKI 기술 지식 베이스

4. 페이지네이션된 데이터에 대한 접근 인가

원문 보기 위키 갱신

GET /todos 인가는 업데이트 엔드포인트와 비슷하게, 각 리소스의 특성에 기반해 접근을 인가할 수 있어야 해요. 그러나 이 엔드포인트에서는 리소스 목록을 인가해야 해요.

출처: 문서

본문

GET /todos 인가는 업데이트 엔드포인트와 비슷하게, 각 리소스의 특성에 기반해 접근을 인가할 수 있어야 해요. 그러나 이 엔드포인트에서는 리소스 목록을 인가해야 해요.

한 가지 가능한 해결책은 일괄 처리(batching) 기능을 활용해 모든 todo를 인가한 다음, 결정이 ALLOW인 것만 반환하는 것이에요.

router.get('/todos', async (req, res) => {  const credentials = await httpAuth.credentials(req, { allow: ['user'] });  res.json(getAll());  const items = getAll();  const decisions = await permissions.authorize(    items.map(({ id }) => ({      permission: todoListReadPermission,      resourceRef: id,    })),    { credentials },  );  const filteredItems = decisions.filter(    decision => decision.result === AuthorizeResult.ALLOW,  );  res.json(filteredItems);});

이 접근 방식은 간단한 경우에는 작동하지만 단점이 있어요. 모든 요소를 미리 가져와 하나씩 인가하도록 강제해요. 이는 데이터 소스가 현재 처리하고 있는 페이지네이션 같은 문제를 플러그인 구현이 처리하도록 강제해요.

이 상황을 피하기 위해 권한 프레임워크는 데이터 소스 자체에서 항목을 필터링하는 것을 지원해요. 이 튜토리얼의 이 부분에서는 그 동작을 사용하는 데 필요한 단계를 설명할 거예요.

참고

이렇게 인가 필터링을 수행하려면 데이터 소스가 AND, OR, NOT 연산자로 필터를 논리적으로 결합할 수 있어야 해요. 권한 프레임워크가 반환하는 조건부 결정은 중첩 객체를 사용해 조건을 결합해요. 필터 API를 처음부터 구현한다면 상호운용성을 위해 같은 형태를 사용하는 것을 권장해요. 그렇지 않으면 중첩 객체를 자체 형식으로 변환하는 함수를 구현해야 해요.

읽기 권한 만들기

플러그인에 또 다른 권한을 추가해 보겠어요.

plugins/todo-list-backend/src/service/permissions.ts

import { createPermission } from '@backstage/plugin-permission-common';export const TODO_LIST_RESOURCE_TYPE = 'todo-item';export const todoListCreatePermission = createPermission({  name: 'todo.list.create',  attributes: { action: 'create' },});export const todoListUpdatePermission = createPermission({  name: 'todo.list.update',  attributes: { action: 'update' },  resourceType: TODO_LIST_RESOURCE_TYPE,});export const todoListReadPermission = createPermission({  name: 'todos.list.read',  attributes: { action: 'read' },  resourceType: TODO_LIST_RESOURCE_TYPE,});export const todoListPermissions = [  todoListCreatePermission,  todoListUpdatePermission,  todoListReadPermission,];

조건부 정책 결정 사용

평소처럼 권한 통합을 갱신해 새 권한을 포함하는 것부터 시작할 거예요.

plugins/todo-list-backend/src/plugin.ts

import {  TODO_LIST_RESOURCE_TYPE,  todoListCreatePermission,  todoListUpdatePermission,  todoListReadPermission,} from '@internal/plugin-todo-list-common';// ...permissionsRegistry.addResourceType({  resourceRef: todoListPermissionResourceRef,  permissions: [todoListCreatePermission, todoListUpdatePermission],  permissions: [    todoListCreatePermission,    todoListUpdatePermission,    todoListReadPermission,  ],  rules: Object.values(rules),  getResources: async resourceRefs => {    return Promise.all(resourceRefs.map(getTodo));  },});

지금까지 우리는 결과를 반환하기 전에 조건부 결정을 평가하는 PermissionsService.authorize 메서드만 사용했어요. 이 단계에서는 플러그인 안에서 조건부 결정을 평가하기를 원하므로 대신 PermissionsService.authorizeConditional을 사용할 거예요.

plugins/todo-list-backend/src/service/router.ts

import {  createConditionTransformer,  ConditionTransformer,} from '@backstage/plugin-permission-node';import { add, getAll, getTodo, update } from './todos';import { add, getAll, getTodo, TodoFilter, update } from './todos';import { todoListPermissionResourceRef } from './rules';import {  todoListCreatePermission,  todoListUpdatePermission,  todoListReadPermission,} from '@internal/plugin-todo-list-common';// ...const transformConditions = createConditionTransformer(  permissionsRegistry.getPermissionRuleset(todoListPermissionResourceRef));router.get('/todos', async (_req, res) => {router.get('/todos', async (req, res) => {  const credentials = await httpAuth.credentials(req, { allow: ['user'] });  const decision = (    await permissions.authorizeConditional([{ permission: todoListReadPermission }], {      credentials,    })  )[0];  if (decision.result === AuthorizeResult.DENY) {    throw new NotAllowedError('Unauthorized');  }  if (decision.result === AuthorizeResult.CONDITIONAL) {    const filter = transformConditions(decision.conditions);    res.json(getAll(filter));  } else {    res.json(getAll());  }  res.json(getAll());});

조건부 결정을 처리하는 과정을 더 쉽게 하기 위해 권한 프레임워크는 createConditionTransformer 헬퍼를 제공해요. 이 함수는 권한 규칙 배열을 받아, 각 규칙에 정의된 toQuery 메서드를 사용해 조건을 플러그인이 필요로 하는 형식으로 변환하는 transformer 함수를 반환해요.

우리 플러그인에서 사용하는 TodoFilter가 조건 객체의 구조와 일치하므로 조건 transformer의 출력을 직접 전달할 수 있어요. 필터가 다르게 구조화되어 있다면 API에 전달하기 전에 더 변환해야 해요.

인가된 읽기 엔드포인트 테스트

todoListReadPermission 권한을 받을 때마다 조건부 결과를 반환하도록 권한 정책을 갱신해 보겠어요. 이 경우 todoListUpdatePermission에 대해 반환된 결정을 재사용할 수 있어요. Getting Started 단계에서 만든 권한 정책 모듈의 CustomPolicy 클래스를 갱신하세요.

import {  AuthorizeResult,  PolicyDecision,  isPermission,} from '@backstage/plugin-permission-common';import {  PermissionPolicy,  PolicyQuery,  PolicyQueryUser,} from '@backstage/plugin-permission-node';import { UserInfoService } from '@backstage/backend-plugin-api';import {  todoListCreatePermission,  todoListUpdatePermission,  todoListReadPermission,} from '@internal/plugin-todo-list-common';import {  todoListConditions,  createTodoListConditionalDecision,} from '@internal/plugin-todo-list-backend';export class CustomPolicy implements PermissionPolicy {  constructor(private readonly userInfo: UserInfoService) {}  async handle(    request: PolicyQuery,    user?: PolicyQueryUser,  ): Promise<PolicyDecision> {    if (isPermission(request.permission, todoListCreatePermission)) {      return {        result: AuthorizeResult.ALLOW,      };    }    if (isPermission(request.permission, todoListUpdatePermission)) {    if (      isPermission(request.permission, todoListUpdatePermission) ||      isPermission(request.permission, todoListReadPermission)    ) {      const userEntityRef = user        ? (await this.userInfo.getUserInfo(user.credentials)).userEntityRef        : '';      return createTodoListConditionalDecision(        request.permission,        todoListConditions.isOwner({          userId: userEntityRef,        }),      );    }    return {      result: AuthorizeResult.ALLOW,    };  }}

권한 정책 변경 사항을 저장하면 UI에 자신이 만든 todo 항목만 표시되어야 해요.

더 알아보기 (Learn more)