【问题标题】:Authorize React Components for Users by Role/Permissions通过角色/权限为用户授权 React 组件
【发布时间】:2020-01-17 03:17:02
【问题描述】:

我用 React 和 ASP .Net Core 做了一个 SPA。 身份验证通过 Bearer-Tokens(使用 IdentityServer4 和 oidc-client)完成,授权在服务器端完成(使用 PolicyServer)。

我能做到:

  • 通过将我的用户重定向到我的 React 来验证我的用户 IdentityServer-登录页面。
  • 授权我的登录用户使用我的 Rest-Api,前提是他有所需的 PolicyServer 的角色/权限。使用 axios 访问 Rest-Api 也没有任何问题。

登录后在我的应用程序中,有多个可见的按钮,单击它们会打开一个组件。 我的问题是:在按下按钮后,我找不到授权用户访问这些组件的好方法。

我需要检查用户是否具有所需的角色/权限或声明。

在没有找到任何方法后,我想到了使用 axios 来检查一个 HttpGet,如果用户被授权则返回。 但是在客户端有一个 axios-command 很可能不是一个好方法。

有人可以帮忙吗?

【问题讨论】:

    标签: javascript reactjs asp.net-core authorization


    【解决方案1】:

    我个人使用 HOC(Higer Order Component)/Wrapper 来授权路由。

    HOC 是做什么的?

    • 我们将相应路由所需的组件传递给 HOC,并传递所需的其他数据。在路线的 component 道具中,将 HOC 传递为 component={WithAuthorization(MyComponent,miscData) />

    • 它允许我们有条件地渲染组件。简而言之如果授权逻辑失败,则渲染回退组件或渲染传递给它的实际组件

    我的典型 HOC 如下所示:

    export const WithAuthorization = (Component, menuObj) => {
        /* For route's data, you can have menu's metadata */
        const { menuId, action, menuName } = menuObj;
        class UserMode extends React.Component {
            state = {
                isAllowedToAccess: false,
                isProcessCompleted: false,
                menuPermissionObj: {},
                masterAccess: false
            };
    
            async componentDidMount() {
                const permission = this.props.auth.menuPermissions
                    ? this.props.auth.menuPermissions
                    : JSON.parse(localStorage.getItem('menu_permissions'));
                await this.checkAuthorization(permission);
            }
    
            shouldComponentUpdate(nextProps, nextState) {
                /* 
                Restrict re-render until there is 
                menuPermissions object available for user profile
                */
                if (this.props.auth.menuPermissions) {
                    return true;
                } else {
                    return false;
                }
            }
    
            /* Business logic to check roles & permissions for the user and restrict them */
            checkAuthorization = permissionArr => {
                let access = false;
                let menuPermissionObj = {};
    
                /* PERFORM YOUR BUSINESS LOGIC HERE */
                function apiCall(){
                /* return true or false */
                }
                access = apiCall(); // either true or false depending on API call
    
                console.log('has access', access);
                this.setState({
                    isAllowedToAccess: access,
                    isProcessCompleted: true,
                    menuPermissionObj
                });
            };
    
            render() {
                const {
                    isProcessCompleted,
                    isAllowedToAccess,
                    menuPermissionObj,
                    masterAccess
                } = this.state;
                return isProcessCompleted ? (
                    isAllowedToAccess ? (
                        <Fragment>
                            <Component
                                {...this.props}
                                menuAccess={menuPermissionObj}
                                masterAccess={masterAccess}
                            />
                        </Fragment>
                    ) : (
                        <Redirect to="/dashboard" />
                    )
                ) : (
                    <Loading state={this.state} />
                );
            }
        }
    
        /* OPTIONAL: Use react-redux's connect if you want to access redux's store data */
    
        return UserMode;
    };
    

    【讨论】:

    • 我使用了你的方法,但我不太满意,在“checkAuthorization”中,我使用 axios 来获取用户是否拥有数据库内 menuObj 的权限。是否可以在 Web 调试器中更改值(例如将返回的布尔值从 false 更改为 true)?我试过了,但没有成功,但我不知道它是否容易被绕过。
    • 绕过包装器的 API。从 main App.js 调用它,如果你将它传递给包装器,它会将数据存储在 redux 中。
    • 你写的 - like the returning bool from false to true) 这意味着什么?您想消除shouldComponentUpdate() 生命周期过程吗?
    • 不,我的意思是在“checkAuthorization”中,我从“axios”-Query 的结果中设置了“access”-Value。
    • 是的,这就是为什么我在评论中写成:“编写您的业务逻辑”,最终将计算出access 作为truefalse 的结果
    【解决方案2】:

    使用 IdentityServer 和 .Net Core 身份,您可以执行以下操作:

    1. 将角色添加到令牌意味着通过在 Startup.cs 中注册临时服务来将角色声明插入到已发布的声明中。

    2. 现在,当您在 AuthorizeService.js 中调用 user.profile 时,您会注意到添加的属性“role”,您现在要做的就是根据该角色/权限验证和呈现您的组件。

    3. 假设您只需要授予“成员”对某个组件的访问权限:

       export class NavMenu extends Component {
      static displayName = NavMenu.name;
      
         constructor(props) {
           super(props);
      
           this.toggleNavbar = this.toggleNavbar.bind(this);
           this.state = {
             collapsed: true,
             isAuthenticated: false,
             role: null,
           };
         }
         componentDidMount() {
           this._subscription = authService.subscribe(() => this.populateState());
           this.populateState();
         }
      
         componentWillUnmount() {
           authService.unsubscribe(this._subscription);
         }
         async populateState() {
           const [isAuthenticated, user] = await Promise.all([
             authService.isAuthenticated(),
             authService.getUser(),
           ]);
           this.setState({
             isAuthenticated,
             role: user && user.role,
           });
         }
      
         toggleNavbar() {
           this.setState({
             collapsed: !this.state.collapsed,
           });
         }
      
         render() {
           return (
             <header>
               <Navbar
                 className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3"
                 light
               >
                 <Container>
                   <NavbarBrand tag={Link} to="/">
                     SPA.Utility.Templates
                   </NavbarBrand>
                   <NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
                   <Collapse
                     className="d-sm-inline-flex flex-sm-row-reverse"
                     isOpen={!this.state.collapsed}
                     navbar
                   >
                     <ul className="navbar-nav flex-grow">
                       <NavItem>
                         <NavLink tag={Link} className="text-dark" to="/">
                           Home
                         </NavLink>
                       </NavItem>
      
                       {this.state.role && this.state.role.includes("Member") ? (
                         <NavItem>
                           <NavLink tag={Link} className="text-dark" to="/counter">
                             Counter
                           </NavLink>
                         </NavItem>
                       ) : null}
                       <NavItem>
                         <NavLink tag={Link} className="text-dark" to="/fetch-data">
                           Fetch data
                         </NavLink>
                       </NavItem>
      
                       <LoginMenu></LoginMenu>
                     </ul>
                   </Collapse>
                 </Container>
               </Navbar>
             </header>
           );
         }
       }
      

    根据用户的角色在渲染整个组件时注意该部分:

    {this.state.role && this.state.role.includes("Member") ? (
                   <NavItem>
                     <NavLink tag={Link} className="text-dark" to="/counter">
                       Counter
                     </NavLink>
                   </NavItem>
                 ) : null}
    

    这是一个在 .Net core3.1 和 reactJs 中使用 RBAC 的示例应用程序,可能会对您有所帮助:https://github.com/HoussemMereghni/SPA-NetCore-React-RBAC

    【讨论】:

      【解决方案3】:

      您可以使用反应路由器设置受保护的路由。更多信息请查看https://tylermcginnis.com/react-router-protected-routes-authentication/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-21
        • 2019-05-16
        • 2021-04-01
        • 2015-11-17
        • 2011-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多