Security & Privacy
如何在 Parse 上创建安全应用并增强类级别安全性
19 分
如何使用 parse 创建一个安全的应用程序 介绍 你好,back4app 社区! 这是来自 joren winge 的客座教程,来自 startup soul http //startup soul com/ 我们帮助初创公司快速构建和推出他们的产品。我们的朋友 @ back4app 请我们向您展示如何在 back4app 上构建一个安全的应用程序。 在这篇文章中,我们将带您了解如何在 back4app 上制作一个安全的待办事项应用程序。安全性很重要。如果您的应用程序有任何起色,您需要确保应用程序的数据是安全的,并且您的系统无法被黑客攻击。 parse 上的安全功能 首先让我们谈谈第一层安全性,acls (访问控制列表)。acls 基本上就是您在创建对象时设置的规则。假设您创建了一个待办事项,在创建时,您可以指定谁可以读取该项目,谁可以写入该项目。您可以指定某些用户能够读取该项目或写入该项目,或者您可以将其中一个设置为公开,这样任何人都可以访问。但是 acls 并不总是有效。 有些情况下,您可能需要一些更复杂的逻辑,而不是简单的 acl。有时您也可能会陷入困境,您可能需要根据条件而不是像 acl 那样绝对地给予某人对对象的访问权限。因此,让我们跳过使用 acl。它们太死板,同时又允许对数据的访问过多。所以我即将告诉您在 back4app 和 parse server 上构建安全应用程序的秘密。准备好了吗? 类级别权限!是的,我们将为数据库中的每个表设置类级别权限。我们将设置的权限级别是完全没有权限。我们将锁定每一个表,以便任何人都不允许读取或写入访问!听起来很极端,我知道,但这是创建安全应用程序的第一步。我们唯一允许的权限是在用户表上,这将用于创建新用户对象,以及用户查看自己的数据,这对于刷新当前用户是必需的。 我们将通过使用 acl 来保护用户不查看其他用户的数据。这是我们唯一一次使用 acl,所以我想它们并不是完全没用。它们是有用的,但不要依赖它们来做所有事情。但是你问我们将如何访问数据?好问题,很高兴你在考虑这个!那么,让客户端以受控方式访问数据的秘密是让每一个客户端与数据库之间的交互都通过云代码函数进行过滤。是的,任何时候你与应用程序进行任何操作时,现在都将通过自定义云代码函数进行。不再使用基于客户端的 pfqueries。 除了注册功能、登录功能、忘记密码功能和登出功能外,你几乎可以跳过使用整个基于客户端的 parse sdk。对于这些,我们仍然会使用原生客户端 sdk。这样更简单。你以前写过云代码吗?没有,你说?好吧,这很简单。它只是 javascript,并且使用 parse javascript sdk,但在你自己应用的服务器上内部运行。事实上,由于 parse server 基于 node js,它与使用 express 编写路由非常相似,但更简单,因为你的查询语言已经安装,云代码函数比整个 node js express 应用更容易编写。 所以我们将做的事情是。我们将使用我已经创建的 ios 待办事项应用。我们不会费心向你展示我是如何创建它的。相反,我们将专注于编写云代码和保护数据库。待办事项应用将是一个安全的应用,你只能访问自己的待办事项,并且只能写自己的待办事项。数据将在服务器上安全,免受恶意流氓客户端的攻击。我还将向你展示如何编写一个安全的 parse 后台作业——基本上与 cron 作业相同——这样你就可以让自动化服务按计划发送和操作你的数据。听起来复杂,但其实并不复杂。想象一下小型服务器机器人,它们按自动化计划执行你想要的任何操作。听起来很酷,对吧?好吧,我们开始吧!!!! 让我们设置 back4app 安全待办事项应用 1\) 在 back4app 上创建一个应用: 在 back4app 上创建一个新应用。将应用命名为‘安全待办应用’。 注意:请遵循 新 parse 应用教程 以了解如何在 back4app 上创建应用。 进入应用的核心设置页面,然后点击编辑应用详情。 禁用名为‘允许客户端类创建’的复选框以禁用客户端类创建,然后点击保存。我们希望限制客户端的操作。 2\) 为用户类设置类级别安全权限: 接下来,我们将为用户类设置权限。进入 back4app 数据库仪表板,点击用户类。然后点击安全选项卡,再点击右上角的齿轮图标。您应该会看到一个菜单,上面写着简单/高级。将滑块切换到高级。然后您应该会看到该类的完整类级别权限。禁用查找复选框。禁用更新和删除复选框。最后,禁用添加字段复选框。然后点击保存。您的安全设置应该如下所示。 3\) 创建 todo 类: 点击创建一个类,并将其命名为 todo。将类类型设置为自定义。 4\) 为 todo 类设置类级别安全权限: 接下来,我们将为 todo 类设置权限。进入 back4app 数据库仪表板,点击 todo 类。然后点击安全选项卡,再点击右上角的齿轮图标。您应该会看到一个菜单,上面写着简单/高级。将滑块切换到高级。然后您应该会看到该类的完整类级别权限。禁用所有选项,然后点击保存。您的安全设置应该如下所示。 5\) 让我们为 todo 类添加一些列: 首先,让我们将 todo 类与用户类连接起来。我们将通过添加 2 列来实现这一点。 第一列将被称为‘user’,并将指向用户类。 接下来,让我们为创建它的用户的对象 id 创建一列。它将是字符串类型,并称为‘userobjectid’。 接下来,让我们创建一列来保存我们的实际待办信息。它也将是字符串类型,并称为‘tododescription’。 让我们创建一个布尔值来保存待办事项的状态。我们称之为‘finished’。 最后,让我们再添加一列来保存您完成待办事项的日期。我们称之为‘finisheddate’,并将其设置为日期类型。 您的 todo 类应如下所示 6\) 让我们来看看客户端: 客户端是一个相当基本的待办应用。它使用内置的解析函数进行登录、创建新用户和重置密码。除此之外,所有内容都是基于云代码并且安全。用户的 acl 也会在他们登录或注册时立即设置,以确保系统的安全。让我们开始编写云代码函数,以在登录或注册时设置用户的 acl。 随时可以访问使用本教程构建的完整 ios 项目,位于这个 github 仓库 您还可以访问为本教程构建的 main js 云代码文件,位于这个 github 仓库。 1 在客户端中,转到 todocontroller swift 并查找函数 setusersaclsnow。此函数在您登录或查看 loggedinviewcontroller swift 时被调用。该函数检查您是否已登录,如果已登录,则调用云函数以设置您的个人用户 acl。 todocontroller swift 1 func setusersaclsnow(){ 2 if pfuser current() != nil{ 3 let cloudparams \[anyhashable\ string] = \["test" "test"] 4 pfcloud callfunction(inbackground setusersacls, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 //print(error debugdescription) 8 if let descrip = error? localizeddescription{ 9 print(descrip) 10 } 11 }else{ 12 print(result as! string) 13 } 14 }) 15 } 16 } 2 现在让我们编写云代码函数 parse server 3 x 1 parse cloud define('setusersacls', async(request) => { 2 let currentuser = request user; 3 currentuser setacl(new parse acl(currentuser)); 4 return await currentuser save(null, { usemasterkey true }); 5 }); parse server 2 x 1 parse cloud define('setusersacls', function (request, response) { 2 var currentuser = request user; 3 currentuser setacl(new parse acl(currentuser)); 4 currentuser save(null, { 5 usemasterkey true, 6 success function (object) { 7 response success("acls updated"); 8 }, 9 error function (object, error) { 10 response error("got an error " + error code + " " + error description); 11 } 12 }); 13 }); 3 这个云代码使用了两个关键特性来确保您的应用安全,即 request user 和 masterkey。 request user 让您访问正在进行云代码调用的用户,并允许您限制该用户的访问。在这种情况下,我们使用它来设置用户的 acl,以限制对当前用户的读取访问。这样只有用户自己可以读取他们自己的信息。类级别的权限甚至阻止当前用户的写入访问。通过这种方式,用户无法修改自己的信息。他们只能通过云代码更改与自己用户相关的内容。用户在首次注册时可能会导入虚假信息,但我建议编写一个云代码函数,在新用户创建后检查用户的信息。内置的解析函数用于创建新用户非常有用,因此我认为这是一个不错的权衡,但您可以在用户注册后立即通过云代码设置用户的默认值。您还可以在云代码中编写许多安全措施,并使用后台作业自动和持续运行它们,以检测在用户首次创建时导入的任何恶意用户信息。如果您想要真正安全,您可以将任何敏感信息,如会员状态或支付信息,存储在与用户表分开的表中。这样用户在创建时无法伪造任何敏感信息。 4 接下来我们来看看如何创建一个待办事项。在客户端中,转到 todocontroller swift 并查找函数 savetodo。这个函数在你创建一个新的待办事项时被调用。该函数接受一个描述待办事项的字符串,并将其保存在数据库中。 todocontroller swift 1 func savetodo(todostring\ string, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["todostring"\ todostring] 4 pfcloud callfunction(inbackground createtodosforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 5 现在让我们编写云代码函数,将待办事项保存在数据库中 parse server 3 x 1 parse cloud define("createtodosforuser", async(request) => { 2 let currentuser = request user; 3 let todostring = request params todostring; 4 let todo = parse object extend("todo"); 5 let todo = new todo(); 6 todo set("user", currentuser); 7 todo set("userobjectid", currentuser id); 8 todo set("tododescription", todostring); 9 todo set("finished", false); 10 return await todo save(null, { usemasterkey true }); 11 }); parse server 2 x 1 parse cloud define("createtodosforuser", function(request, response) { 2 var currentuser = request user; 3 var todostring = request params todostring; 4 var todo = parse object extend("todo"); 5 var todo = new todo(); 6 todo set("user", currentuser); 7 todo set("userobjectid", currentuser id); 8 todo set("tododescription", todostring); 9 todo set("finished", false); 10 todo save(null, { 11 usemasterkey true, 12 success function (object) { 13 response success(\[todo]); 14 }, 15 error function (object, error) { 16 response error("got an error " + error code + " " + error description); 17 } 18 }); 19 }); 6 这个云代码函数创建一个待办事项对象,并将当前用户设置为该对象的所有者。这一点很重要,以便只有创建它的用户才能找到或修改它。通过不允许在客户端创建待办事项,我们强制待办事项对象符合我们的标准,并确保待办事项由创建它的用户拥有。 7 接下来让我们看看如何从服务器检索您创建的待办事项。在客户端中,转到 todocontroller swift 并查找函数 gettodosfordate。此函数在您检索待办事项时被调用。该函数将日期作为参数,并使用它来检索在该日期之前由您创建的待办事项列表,按降序排列。使用日期是一种很好的方式来编写不使用跳过的懒加载查询。跳过在大型数据集上有时会失败。 todocontroller swift 1 func savetodo(todostring\ string, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["date"\ date] 4 pfcloud callfunction(inbackground gettodosforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 8 现在让我们编写云代码函数,以根据起始日期从数据库中检索待办事项。我们查询在参数日期之前创建的待办事项,因此我们使用 'query lessthan',因为日期基本上是随着时间推移而增大的数字。我还在这里包含了一些棘手的代码。假设我们包括了创建待办事项的用户对象,但我们不想与其他用户共享该用户的敏感信息,我们需要从 json 响应中剥离它。因此,我们有一个 for 循环,在其中我们将用户对象从待办事项中取出,删除 json 中的电子邮件和用户名,然后再放回待办事项中。这在无法控制返回哪些字段的情况下(例如包含的用户对象)从 api 调用中删除敏感数据时非常方便。在这种情况下,我们实际上并不需要它,因为此函数只会返回您自己创建的待办事项。我们通过再次使用 currentuser 来查询仅由附加到请求的 currentuser 创建的待办事项。结果按降序返回,以便最新的待办事项首先出现。当您需要懒加载另一批待办事项时,您可以从最后一个待办事项中获取 createdat 日期,并将其用作下一个请求的日期参数。 parse server 3 x 1 parse cloud define("gettodosforuser", async(request) => { 2 let currentuser = request user; 3 let date = request params date; 4 let query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query lessthan("createdat", date); 7 query descending("createdat"); 8 query limit(100); 9 query include("user"); 10 let results = await query find({ usemasterkey true }); 11 if(results length === 0) throw new error('no results found!'); 12 13 let resultsarray = \[]; 14 for (let i = 0; i < results length; i++) { 15 let todo = results\[i]; 16 let tempuser = todo get("user"); 17 let jsonuser = tempuser tojson(); 18 delete jsonuser email; 19 delete jsonuser username; 20 21 jsonuser type = "object"; 22 jsonuser classname = " user"; 23 24 let cleanedtodo = todo tojson(); 25 cleanedtodo user = jsonuser; 26 cleanedtodo type = "object"; 27 cleanedtodo classname = "todo"; 28 resultsarray push(cleanedtodo); 29 } 30 return resultsarray; 31 }); parse server 2 x 1 parse cloud define("gettodosforuser", function(request, response) { 2 var currentuser = request user; 3 var date = request params date; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query lessthan("createdat", date); 7 query descending("createdat"); 8 query limit(100); 9 query include("user"); 10 query find({ 11 usemasterkey true, 12 success function (results) { 13 var resultsarray = \[]; 14 for (var i = 0; i < results length; i++) { 15 var todo = results\[i]; 16 var tempuser = todo get("user"); 17 var jsonuser = tempuser tojson(); 18 delete jsonuser email; 19 delete jsonuser username; 20 21 jsonuser type = "object"; 22 jsonuser classname = " user"; 23 24 var cleanedtodo = todo tojson(); 25 cleanedtodo user = jsonuser; 26 cleanedtodo type = "object"; 27 cleanedtodo classname = "todo"; 28 resultsarray push(cleanedtodo); 29 } 30 response success(resultsarray); 31 }, 32 error function (error) { 33 response error(" error " + error code + " " + error message); 34 } 35 }); 36 }); 9 现在我们有了待办事项,我们可以在应用程序中查看它们,并在需要时将其标记为已完成。接下来让我们讨论这个。 10 要将待办事项标记为完成,只需点击您创建的任何待办事项上的“标记为完成”按钮。这将触发 todocontroller swift 中名为 ‘marktodosascompletedfor’ 的方法,该方法将您选择的待办事项作为参数。它将 todo objectid 作为参数发送到服务器,然后返回更新后的待办事项作为结果。 todocontroller swift 1 func marktodosascompletedfor(todo\ todo, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["todoid"\ todo objectid ?? ""] 4 pfcloud callfunction(inbackground marktodoascompletedforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 11 现在我们将编写云代码来更新这个待办事项。它根据 objectid 查找要更新的待办事项,但它还使用 currentuser 来确保与 objectid 关联的待办事项是由发起查询的用户创建的。这确保您只能查看自己创建的待办事项,因此是安全的。我们限制结果为 1,以确保服务器在找到待办事项后不会继续搜索。还有另一种根据 objectid 查找对象的方法,但我不喜欢使用它,因为如果找不到与 objectid 关联的对象,它可能会返回奇怪的结果。我们还在对象更新时将 'finisheddate' 设置为当前日期。通过仅由此函数设置 finisheddate,我们确保了 finisheddate 是安全的,无法伪造或更改。我们还使用了 'query equalto(“finished”, false)' 来确保只有未完成的待办事项可以被标记为完成并设置 finisheddate。这意味着一旦待办事项被标记为完成,它就不能在以后的日期再次被标记为完成。 parse server 3 x 1 parse cloud define("marktodoascompletedforuser", async(request) => { 2 let currentuser = request user; 3 let todoid = request params todoid; 4 let query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 let todo = await query first({ usemasterkey true }); 9 if(object keys(todo) length === 0) throw new error('no results found!'); 10 todo set("finished", true); 11 let date = new date(); 12 todo set("finisheddate", date); 13 try { 14 await todo save(null, { usemasterkey true}); 15 return todo; 16 } catch (error){ 17 return("getnewstore error " + error code + " " + error message); 18 } 19 }); parse server 2 x 1 parse cloud define("marktodoascompletedforuser", function(request, response) { 2 var currentuser = request user; 3 var todoid = request params todoid; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 query limit(1); 9 query find({ 10 usemasterkey true, 11 success function (results) { 12 if (results length > 0) { 13 var todo = results\[0]; 14 todo set("finished", true); 15 var date = new date(); 16 todo set("finisheddate", date); 17 todo save(null, { 18 usemasterkey true, 19 success function (object) { 20 response success(\[todo]); 21 }, 22 error function (object, error) { 23 response error("got an error " + error code + " " + error description); 24 } 25 }); 26 } else { 27 response error("todo not found to update"); 28 } 29 30 }, 31 error function (error) { 32 response error(" error " + error code + " " + error message); 33 } 34 }); 35 }); 7\) 总结! 这就是全部。你已经构建了一个安全的待办事项应用程序。再次强调,在 parse 服务器上构建安全应用程序的关键是禁用除用户类之外的所有类的类级权限。在用户类上,你禁用所有权限,除了创建和获取。同时确保设置所有用户的 acl,以便用户只能获取自己的数据。然后,所有的交互都通过云代码进行,并使用 request user(即当前用户)进行过滤。所以,你可以在 parse 服务器和 back4app 上构建安全系统。但等等,你会问?后台作业和实时查询呢?嗯,你说得对,所以我将在接下来的两个附加部分中涵盖这些内容。 8\) 附加部分 1\ 后台作业 有时你需要创建一个每小时、每天或每周运行的后台作业。如果你关闭了所有类级权限,后台作业将无法查询数据库,除非它设置正确。这有点棘手,所以我想在这里包含一个示例。在这种情况下,我们将创建一个后台作业,检查数据库中未完成的待办事项,这些待办事项超过 1 年,并自动将它们标记为完成。这里的关键是正确使用 'usemasterkey'。 它必须在 then promise 之前添加到查询中。只需遵循这个模板,你就应该能够轻松编写安全的后台作业。你总是从编写一个你想遍历整个数据库的查询开始,然后确保在出现错误时包含 status error,并以 status success 结束,以确保它完成。你可以在 back4app 上查看日志,以查看后台作业在运行时的工作情况。 parse server 3 x 1 parse cloud job("markunfinishedtodosolderthan1yearasfinished", async(request) => { 2 let date = new date(); 3 let intyear = date getfullyear() 1; 4 let query = new parse query("todo"); 5 query equalto("finished", intyear); 6 query lessthan("createdat", date); 7 8 let todo = await query find({ usemasterkey true }); 9 for (let i = 0; i < results length; i++) { 10 let todo = results\[i]; 11 todo set("finished", true); 12 todo set("finisheddate", date); 13 try { 14 await todo save(null, { usemasterkey true}); 15 } catch (error){ 16 console log("getnewstore error " + error code + " " + error message); 17 } 18 } 19 return "migration completed successfully "; 20 }); parse server 2 x 1 parse cloud define("marktodoascompletedforuser", function(request, response) { 2 var currentuser = request user; 3 var todoid = request params todoid; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 query limit(1); 9 query find({ 10 usemasterkey true, 11 success function (results) { 12 if (results length > 0) { 13 var todo = results\[0]; 14 todo set("finished", true); 15 var date = new date(); 16 todo set("finisheddate", date); 17 todo save(null, { 18 usemasterkey true, 19 success function (object) { 20 response success(\[todo]); 21 }, 22 error function (object, error) { 23 response error("got an error " + error code + " " + error description); 24 } 25 }); 26 } else { 27 response error("todo not found to update"); 28 } 29 30 }, 31 error function (error) { 32 response error(" error " + error code + " " + error message); 33 } 34 }); 35 }); 2 实时查询 有时你需要使用 parse 的实时查询功能,例如用于实时聊天应用。你会想要使用实时查询来查看为你的用户创建的新消息。实时查询基本上就是 parse 使用套接字获取实时更新的方式。这非常方便,但它不会与 find 权限已关闭的类一起工作。因此,在这种情况下,我们将为消息类重新打开 find 权限,而是直接为该消息分配 acl。acl 应该设置为只有接收者可以使用 find 从服务器获取消息。然后你在客户端运行 pf 实时查询,查找你的用户的消息,它将完美运行。如果你处理的是群组消息,那就有点不同。你可以将多个人分配到 acl 上,但这实际上并不扩展。相反,有更好的方法。你将 acl 设置为基于角色 parse role 然后任何你想要让其访问该消息的用户,你只需将他们分配到该 parse role。如果你想阻止他们阅读该组的消息,你只需将他们从该角色中移除。这比从每条消息的 acl 中移除他们要简单得多,并且适用于超级大的群组。这是正确的做法。我不会为此留下代码示例,因为这对本教程来说太复杂了,但也许我会在下一个教程中解释如何做到这一点。感谢你阅读关于 parse 和 back4app 的安全性教程。如果你有问题,请随时 联系我 ,我会很高兴回答它们。 谢谢!joren