CASL 및 Redux를 사용하여 반응하고 동적 권한을 관리합니다.

CASL이란 무엇입니까?



CASL은 역할에 따라 사용자의 권한을 관리할 수 있는 JavaScript 라이브러리입니다.

이 기사에서는 React 및 Redux를 사용하여 Front-End에서 CASL로 권한을 관리하는 방법을 보여줍니다.

프런트 엔드에서 권한을 처리하는 이유는 무엇입니까?



Front-End 개발자로서 우리의 역할 중 하나는 서버로 보내는 요청 수를 줄이는 것입니다.

예를 들어 양식의 프런트 엔드 유효성 검사를 수행하므로 서버에 데이터를 요청할 필요가 없으며 서버는 유효성 검사 오류로 응답합니다.

또한 프런트 엔드에서 권한을 관리합니다. 따라서 사용자는 권한이 없는 특정 API를 요청할 필요가 없습니다. 궁극적으로 우리는 서버와 사용자의 부하를 줄일 것입니다.

That doesn't mean we will eliminate requesting unauthorized permissions APIs totally, We will need some of them in particular cases.



1. 시작하기.



You can download the project repo from HERE

You can find the final result HERE


  • 반응 앱을 만듭니다.

  • npx create-react-app casl-app
    


  • Redux, react-redux 및 redux-thunk를 설치합니다.

  • npm install redux react-redux redux-thunk
    


  • CASL 설치

  • npm install @casl/react @casl/ability
    


    2. 캔 파일 생성.



    새 파일을 만들고 이름을 can.js로 지정하고 다음을 붙여넣습니다.

    can.js

    import { Ability, AbilityBuilder } from "@casl/ability";
    
    const ability = new Ability();
    
    export default (action, subject) => {
      return ability.can(action, subject);
    };
    


    여기서 우리는 Ability 에서 AbilityBuilder@casl/ability 를 가져옵니다.

    그런 다음 Ability()에서 새 인스턴스를 만듭니다.

    그런 다음 나중에 로그인한 사용자의 권한을 확인하는 데 사용할 기본 기능을 내보냅니다.

    3. 스토어에 가입합니다.



    can.js

    import { Ability, AbilityBuilder } from "@casl/ability";
    import { store } from "../redux/storeConfig/store";
    
    const ability = new Ability();
    
    export default (action, subject) => {
      return ability.can(action, subject);
    };
    
    store.subscribe(() => {
      let auth = store.getState().auth;
    });
    
    


    스토어를 가져와 내부에서 구독합니다can.js .

    여기 상점에서 auth를 받고 있습니다.
    그리고 이것은 내 redux 폴더와 파일입니다.

    store.js

    import { createStore, applyMiddleware, compose } from "redux";
    import createDebounce from "redux-debounced";
    import thunk from "redux-thunk";
    import rootReducer from "../rootReducer";
    
    const middlewares = [thunk, createDebounce()];
    
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    const store = createStore(
      rootReducer,
      {},
      composeEnhancers(applyMiddleware(...middlewares))
    );
    
    export { store };
    
    


    rootReducer.js

    import { combineReducers } from "redux";
    import authReducer from "./auth/authReducer";
    
    const rootReducer = combineReducers({
      auth: authReducer,
    });
    
    export default rootReducer;
    
    


    authReducer.js

    const INITIAL_STATE = {};
    
    const authReducer = (state = INITIAL_STATE, action) => {
      switch (action.type) {
        case "LOGIN":
          return { ...state, ...action.payload };
        case "LOGOUT":
          return {};
        default:
          return state;
      }
    };
    
    export default authReducer;
    
    


    authActions.js

    export const login = (user) => async (dispatch) => {
      dispatch({
        type: "LOGIN",
        payload: {
          id: 1,
          name: "Youssef",
          permissions: ["add_users", "delete_users"],
        },
      });
    };
    
    export const logout = () => async (dispatch) => {
      dispatch({
        type: "LOGOUT",
      });
    };
    
    


    로그인 작업에서 ID, 이름 및 권한 배열 개체로 페이로드를 하드 코딩하고 있습니다.

    permissions array doesn't have to be like that more about that later.



    4. can.js에 defineRulesFor 함수 추가




    import { Ability, AbilityBuilder } from "@casl/ability";
    import { store } from "../redux/storeConfig/store";
    
    const ability = new Ability();
    
    export default (action, subject) => {
      return ability.can(action, subject);
    };
    
    store.subscribe(() => {
      let auth = store.getState().auth;
      ability.update(defineRulesFor(auth));
    });
    
    const defineRulesFor = (auth) => {
      const permissions = auth.permissions;
      const { can, rules } = new AbilityBuilder();
    
      // This logic depends on how the
      // server sends you the permissions array
      if (permissions) {
        permissions.forEach((p) => {
          let per = p.split("_");
          can(per[0], per[1]);
        });
      }
    
      return rules;
    };
    

    defineRulesFor를 인수로 사용하는 auth 함수를 만들었고 우리가 구독하고 있는 상점에서 이 auth를 가져옵니다.
    그래서 ability.update(defineRulesFor(auth)) 본문에 store.subscribe()를 추가했습니다.

    그런 다음 can에서 rulesnew AbilityBuilder()를 얻습니다.

    그리고 내 권한 배열은 strings로 구분된 _의 숫자이기 때문에

    permissions: ["add_users", "delete_users"]
    


    이 문자열을 분할하고 actionsubjectcan 함수에 전달합니다.

    이 논리는 서버가 다음과 같은 ID만 보내는 경우 변경될 수 있습니다.

    const permissions = [2, 3, 5, 7];
    if (permissions) {
      permissions.forEach((p) => {
        if (p === 3) can("add", "users");
        if (p === 7) can("delete", "users");
      });
    }
    
    


    또는 미리 정의된 역할일 수도 있습니다.

    const role = "Editor";
    if (role === "Editor") {
      can("add", "users");
      can("delete", "users");
    }
    
    


    등등.

    5. 권한 확인.



    App.jsx 내부의 권한을 확인합니다.

    App.jsx

    import React, { useState } from "react";
    import { useDispatch, useSelector } from "react-redux";
    import { login, logout } from "./redux/auth/authActions";
    import CAN from "./casl/can";
    
    export default () => {
      const dispatch = useDispatch();
      const { auth } = useSelector((state) => state);
    
      // rerender the component when `auth` changes
      useState(() => {}, [auth]);
    
      return (
        <React.Fragment>
          <h1>Welcome, {auth?.name || "Please Login!"}</h1>
    
          {CAN("add", "users") && (
            <button
              onClick={() => {
                alert("User Added!");
              }}>
              Add User
            </button>
          )}
          {CAN("delete", "users") && (
            <button
              onClick={() => {
                alert("User Deleted!");
              }}>
              Delete User
            </button>
          )}
          <div>
            <button
              onClick={() => {
                dispatch(login());
              }}>
              Login
            </button>
            <button
              onClick={() => {
                dispatch(logout());
              }}>
              Logout
            </button>
          </div>
        </React.Fragment>
      );
    };
    
    


    여기서는 로그인한 사용자의 권한에 따라 버튼을 표시하고 있습니다.

    You need to rerender the component when the auth changes using useEffect.



    최종 결과 확인HERE

    좋은 웹페이지 즐겨찾기