pg-promise和行级安全

问题描述

我正在考虑通过我们的node express + pg-promise + postgres服务实现行级安全性。

我们尝试了几种方法,但均未成功:

  1. 创建一个getDb(tenantId)包装器,该包装器将调用SET app.current_tenant ='$ {tenantId}';`sql语句,然后返回db对象
  2. getDb(tenantId)包装器每次都会获取一个新的数据库对象-这适用于一些请求,但最终会导致过多的数据库连接和错误输出(这是可以理解的,因为它没有使用pg-promise的连接池管理)
  3. getDb(tenantId)包装器,该包装器使用名称值(映射)存储每个租户的数据库连接列表。这会工作一会儿,但最终会导致数据库连接过多。
  4. 利用initOptions> connect事件-尚未找到一种方法来获取当前请求对象(然后设置tenant_id)

有人(希望vitaly-t :))可以建议在连接中运行所有sql查询之前注入当前租户的最佳策略。

非常感谢您

这是一个简短的代码示例:

import React,{ Component } from 'react';
import { Card } from 'antd';
import axios from 'axios';
import { notification} from "antd";

export default class AddDepartment extends Component {

    constructor(props) {
        super(props);
        this.state = { 
            admins:[],empId:[],admin_id:'',dept_id:'',dept_name:'',dept_manager_name:''
         }
    }

    componentDidMount(){
        this.fetchAdmins();
        this.fetchAdmins();
    }

    //Admin data as foreign key
    fetchAdmins = () =>{
        axios.get("http://127.0.0.1:8000/admin-list/").then(res=>
        {
            const admins=res.data;
            this.setState({admins});
        }
        )
    }
    fetchEmployee = () => {
        axios.get("http://127.0.0.1:8000/employeeid/").then(res=>
        {
            const empId=res.data;
            this.setState({empId});
        }
        )

    }
    
    formData = (e) => {
        this.setState({ [e.target.name]: e.target.value });
        this.setState({ radstatus: !this.state.radstatus });

        console.log(
            this.state.dept_name,this.state.dept_manager_name
        );
        console.log(this.state.radstatus);
        console.log(this.state.event);
    };

    drop = (e) =>{
        this.setState({[e.target.name]:e.target.value})
        console.log(this.state.event)
    }

    onCreateEmp = () => {
        console.log();
    }

    formSubmit = (e) =>{
        e.preventDefault();

        const deptdata ={
            admin_id:this.state.admin_id,dept_id:this.state.dept_id,dept_name:this.state.dept_name,dept_manager_name:this.state.dept_manager_name,}

        console.log(deptdata)

        const args = {
            description:
              "Data added successfully",duration: 0,};
          notification.open(args);

        var url = "http://127.0.0.1:8000/department-Create/";

        fetch(url,{
            method: 'POST',headers:{
              'Content-type':'application/json',},body: JSON.stringify(deptdata)
          }).then((response) => {
            alert(response)
          }).catch(function(err){
            alert(err)
          })
        }
   
render() {
    return (
        <div>
            <div className="row">

            <div className="col main">
                <h1 
                    style={{
                        color: "DodgerBlue",fontSize: '30px',fontWeight:"bold"}}>
                    <center>ADD DEPARTMENT</center>
                </h1>
                <br></br><br></br>
                <form  onSubmit= {this.formSubmit}
                        style={{
                            fontWeight:"bold"}}
                            >
                <Card style={{ width: 1000,}}>

                         <br></br>Admin ID :<select required onChange={this.formData} id="admin" name="admin_id" style={{border: "3px solid #ccc",float: "right",width: "68%",height:45}}>
                                    {this.state.admins.map((ad) =>{
                                        const adId = ad.id;
                                        const adName = ad.username;

                                        return(
                                        <option value={ad.id}>{ad.username}</option>
                                        );
                                    })}
                        </select> 
                       
                        <br></br><br></br>
                        <br></br>Department ID : <input required style={{border: "3px solid #ccc",height:30}} type="text" onChange= {this.formData} name="dept_id"></input><br></br><br></br>
                        Name : <input required style={{border: "3px solid #ccc",height:30}} type="text" onChange= {this.formData} name="dept_name" ></input><br></br><br></br>
                        Manager of Department : <input required style={{border: "3px solid #ccc",height:30}} type="text" onChange= {this.formData} name="dept_manager_name" ></input><br></br><br></br>
                        
                        <br></br><br></br>
                    
                </Card>
            <div>
            <br></br><br></br><br></br>
                
            <input style={{width:"100%",cursor: "pointer",backgroundColor:"DodgerBlue",border:"none",padding:"12px 28px",margin:"2px 1px",borderRadius:"2px",fontWeight:"bold"}} type="submit" value="Add Department"></input>
            <button>DEMO</button>
            </div>
            </form>
        </div>   
        

    </div>
    </div>
    )
}
}

解决方法

SET操作是受连接限制的,即该操作仅在当前连接会话持续时才生效。对于池产生的新连接,您需要重新应用设置。

控制当前连接会话的标准方法是通过任务:

await db.task('my-task',async t => {
    await t.none('SET app.current_tenant = ${tenantId}',{tenantId});

    // ... run all session-related queries here
});

或者,如果需要进行交易,则可以改用方法tx

但是,如果您全局知道tenantId,并且希望它自动传播到所有连接,则可以使用事件connect

const initOptions = {
    connect(client) {
        client.query('SET app.current_tenant = $1',[tenantId]);
    }
};

后者是一种经过深思熟虑的解决方法,但是它确实可靠地工作,具有最佳性能并且避免了创建额外的任务。

还没有找到一种方法来控制当前请求对象(然后设置tenant_id)

对于任何HTTP库来说,这都应该非常简单,但这不在本文的讨论范围之内。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...