问题描述
我正在考虑通过我们的node express + pg-promise + postgres服务实现行级安全性。
我们尝试了几种方法,但均未成功:
- 创建一个getDb(tenantId)包装器,该包装器将调用SET app.current_tenant ='$ {tenantId}';`sql语句,然后返回db对象
- getDb(tenantId)包装器每次都会获取一个新的数据库对象-这适用于一些请求,但最终会导致过多的数据库连接和错误输出(这是可以理解的,因为它没有使用pg-promise的连接池管理)
- getDb(tenantId)包装器,该包装器使用名称值(映射)存储每个租户的数据库连接列表。这会工作一会儿,但最终会导致数据库连接过多。
- 利用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库来说,这都应该非常简单,但这不在本文的讨论范围之内。