API服务应该由谁发送用户激活电子邮件,是API服务还是客户端应用程序?

19

我正在尝试开发一个REST API web服务。我有一个关于如何处理用户激活电子邮件的问题。目前,API服务处理电子邮件发送。

以下是我目前拥有的流程:

  1. 用户通过客户端应用程序注册
  2. 客户端应用程序POST到API服务
  3. API服务验证并将用户添加到数据库中
  4. API服务发送给用户一个激活链接
  5. 用户点击激活链接,将进入客户端应用程序激活页面
  6. 客户端应用程序激活页面POST到API服务
  7. 完成

这里是我目前看到的问题:

由于API服务目前正在发送电子邮件,因此客户端应用程序无法控制电子邮件的外观和感觉。而且电子邮件中可能有应该指向客户端应用程序的URL。


另一种选择是,代替API服务发送激活电子邮件,它将返回激活密钥给客户端应用程序。然后,客户端应用程序将能够向用户发送激活电子邮件。

我看到这种策略存在两个问题:

  • 安全性,因为激活密钥现在暴露给客户端应用程序。
  • 不DRY,因为每个客户端都可能负责电子邮件发送。

您认为处理这个问题的最佳方法是什么?

我希望允许客户端应用程序定制他们的电子邮件,并包括特定于客户端的URL(激活页面)。


1
为什么客户端不能在用户注册时发送有关电子邮件的美容信息?另外,您使用的平台是哪个,客户端能够发送电子邮件? - chiliNUT
1
这是一个Rest API服务,那么你的意思是说我应该接受一个带有HTML/CSS的JSON对象来创建电子邮件? - user742736
你对“外观和感觉”的要求不是很具体。如果你想让客户端控制电子邮件的任何内容,为什么不将其作为JSON发送到服务器呢? - chiliNUT
是的,这就是我的意思。当客户端应用程序POST注册详细信息时,将HTML/CSS作为JSON发送回来。 - user742736
1
我认为他们都不应该发送电子邮件,你可以使用消息队列(如rabbitmq或zeromq)。当你在API端添加用户到数据库时,应该向队列中添加一条带有必要数据的消息,然后由消费者发送电子邮件。你可以通过消息中的类型标志来处理项目中所有的电子邮件需求。 - engvrdr
显示剩余2条评论
8个回答

12

TL;DR

为开发者创建一个小型服务,让他们在POST到您的激活API时声明要使用哪个模板。


问题摘要:

  • 每个客户端应用程序的电子邮件外观都应不同
  • 只需实现一次发送邮件即可
  • 解决方案应安全

每次电子邮件都不需要看起来都不同。所以没必要在POST请求中附带电子邮件格式。

可以尝试以下两种方法之一:

1 创建一个单独的API端点来定义模板,让客户端应用程序在POST请求激活时选择其中一个。

这种方式并不完全安全,特别是如果你想接受来自客户端应用程序的HTML时,会带来挑战。

推荐的解决方案:

2 为开发者创建一个工具(位于他们获取API密钥的同一网站上),该工具接受模板并帮助创建它们。客户端应用程序在POST请求激活时可以选择其中一个。请求正文的片段类似于:

...
"template": "foobar-app",
"fields": {
    "title": "Welcome to foobar app",
    "username": "jim78"
}
...

字段中不允许使用HTML。

这使您可以使用开发人员准备的预定义模板,由您的电子邮件发送服务使用,并且客户端应用程序中的任何错误都不会导致电子邮件变得不安全。此外,您还可以获得一个地方,在该位置可以处理和测试模板。(开发人员可以将它们发送给自己进行调试 - 制作电子邮件模板是很糟糕的,请相信我)

将来,您将能够更好地支持您的开发人员/客户,并准备一组在多个邮件客户端中测试过的工作模板。


2
我认为实现这一点的方法相当不错。我采用了JSON Web tokens的方法,并将其应用于激活链接。下面是工作原理:
我有两个web服务器,一个处理REST API,另一个处理spa。
用户注册后,请求将发送到API。然后将响应返回到SPA,在此处,如果成功,则向SPA后端发送请求,该请求使用用户凭据、令牌目的(在这种情况下是验证电子邮件地址)和到期日期签署令牌。
该令牌将发送到用户的电子邮件地址,但是在REST服务器上,有一个接收路由将解码令牌,并验证电子邮件地址(如果有效)。
这意味着从技术上讲,只有第一方客户端能够验证电子邮件地址,因为他们是唯一知道密码密钥的人。如果密码密钥被任意分发,那么任何人都可以验证自己的电子邮件地址。
希望对你有所帮助!
编辑:另一种方法是传递使用handlebars或其他东西构建的模板,该模板将变量替换为实际值。然后让REST api进行渲染并通过电子邮件发送。(在我看来,这可能是最好的方式)

2
关于安全和信任的一点。通常您会发送包含激活码的url链接的激活电子邮件。该电子邮件的目的是验证电子邮件地址有效,并且用户可以访问该电子邮件。用户唯一能够收到验证链接的方式是通过电子邮件。
如果您将激活链接返回给客户端,则任何具有API访问权限的人都可以访问激活码。如果他们有链接,他们可以绕过验证过程。如果您有Web应用程序,则这非常容易,因为他们只需进入浏览器开发人员模式即可查看链接。如果您有一个fat client,则如果您没有使用像https这样的加密,则它们可能会窥探网络。如果他们非常专注,他们还可以反编译您的二进制文件(这就是为什么您不要在二进制文件中存储密钥的原因)。
后端永远不应信任客户端实施安全程序,因为它永远不知道何时已被攻击。安全且正确的方法是在服务器端执行激活电子邮件。另一种看待这个问题的方式是,客户端说“是的,用户已经通过身份验证,请给我所有数据”。
至于模板,上面有很多好的答案。我建议拥有一个模板目录和可替换参数的列表。

1

我会在第二步中向服务器发送额外的帖子数据:

"mail":{
  "placeholder":"someStringChoosenByClientWhichWillBeReplaceByActivationCode",
  "subject":"Hey there, please activate",
  "ishtml":false,
  "body":"SSdtIHRyeWluZyB0byBkZXZlbG9wIGEgUkVTVCBBUEkgd2ViIHNlcnZpY2UuIEkgaGF2ZSBhIHF1ZXN0aW9uIGFib3V0IGhvdyB0byBoYW5kbGUgdXNlciBhY3RpdmF0aW9uIGVtYWlsLiBDdXJyZW50bHksIHRoZSBBUEkgc2VydmljZSBoYW5kbGVzIGVtYWlsIHNlbmRpbmcu"
  "attachments":[
                  {
                     "content-type":"image/png",
                     "filename":"inline_logo.png",
                     "content":"base64_data_of_image"
                  }
                ]
}

这将允许客户完全控制发送的消息,但激活过程(邮件生成和传递)仍由服务处理。
除了激活密钥外,每个用户的所有内容都可以由客户端生成(例如使用“Hello XYZ”作为主题)。
我不确定是否允许HTML邮件("ishtml":false,),这取决于您的应用程序以及您想要花费的时间。

1
你的 API 可以拥有一个 IEmailBodyFormatter 对象,该对象作为参数传递到 API 调用中....

0

允许客户管理自己的电子邮件模板。当他们发布新用户注册时,允许他们指定要使用哪个模板。然后您的应用程序将发送电子邮件消息,但客户可以控制其外观。

POST /email-templates
{
    "subject": "Complete Your Registration",
    "body": "<html>Follow this link to complete your registration: {activationLink}. It is valid for 45 minutes.</html>"
}

POST /registration-requests
{
    "name": "John Q. Public",
    "emailTemplate": "/email-templates/45"
}

这似乎存在安全问题。该服务可能被用于向人们发送恶意消息。最好在定义模板时以更安全的方式进行,最好还包括获取API密钥的过程。 - naugtur
@naugtur,问题出在哪里?只有客户端才能创建/修改模板。客户端只能修改或指定他们可以看到的模板,这些模板应该被锁定为他们创建的模板。假设资源已经得到了适当的保护和检查,我不明白问题出在哪里。 - Eric Stein
在多个API中,客户端应用程序使用映射到特定用户的令牌,而电子邮件模板实际上与客户端应用程序或客户端应用程序开发人员具有1对1的关系。如果应用程序可以定义模板,则应用程序中的任何错误都可能被利用以发送恶意内容。当电子邮件模板在客户端应用程序之外定义时,它们与开发人员自己使用的帐户一样安全,以控制API密钥等。另外,请查看我的答案。我是在建设性地发言 ;) - naugtur

0

我认为正确的方式是向客户端公开激活密钥,让其自行处理。

您还可以添加另一个端点来发送激活密钥给用户。

返回用户(URL类似于User/{userid},其他资源URL类似于User/{userid}/ActivationKey)。

User (POST) 

这可以返回当前用户和其他资源,如电子邮件、激活等。 有关密钥的信息(如日期、过期等),请参阅相关文档。

User/{userid}/ActivationKey

从那里,你可以用以下方式扩展它:

预览激活邮件:

User/{userid}/ActivationKey/Email (GET) 

更新激活电子邮件的模板、SMTP服务器等电子邮件相关内容:

User/{userid}/ActivationKey/Email (PUT)

创建(并发送)激活电子邮件,可以选择发送日期或其他发送选项(文本-HTML版本等):

User/{userid}/ActivationKey/Email (POST)

如果需要的话,您可以列出所有发送的电子邮件并在另一个端点中预览它们。

User/{userid}/Emails (GET)
User/{userid}/Emails/{emailid} (GET)

0

我加入了nauktur的想法,让客户发送电子邮件模板给您。(并且+1,因为我同意邮件“开发”的糟糕程度)。

但是为什么要这么复杂?客户端应用程序意味着开发人员,所以为什么不让他们提供您的默认模板(带有HTML),让他们随意玩耍如果他们想的话,然后将他们喜欢的版本发送给您?
对您来说并不需要太多工作(只需在客户表中添加一个新字段和一个新路由),而对他们来说则提供了很多选项。

以下是一个基本示例,我们将公开一些参数,以便他们可以在不必了解它们的情况下玩弄HTML:

  • app.name
  • app.description
  • activation_code
  • user.* 注册信息

基本模板

{
  title: "Your activation code for %{app.name}",
  body: "<p>Hi, you've been registered on %{app.name}.
    <p>%{app.description}</p>
    <p>Follow <a href="%{activation_code}">this link to confirm your inscription</a>."
}

注册新模板

然后客户说:“我更喜欢一个更简单的邮件,但是我希望他的名字在里面!”
[PUT] /api/email/templates/client_id

{
  title: "Your activation code",
  body: "<p>Hi %{user.fullname}, Follow <a href="%{activation_code}">this link to confirm your inscription</a>."
}

在这里,让他们玩一下 HTML 吧,它允许更多个性化设置。
除非他们搞砸了,不然对客户没什么损害,毕竟他们是自己的客户。

安全问题

有人指出攻击者可能会访问客户端应用程序的令牌,并在模板中注入恶意内容。首先,如果令牌泄漏,风险已经非常高了,这可能是您最不担心的问题。但是,如果您担心这个问题,禁止使用 img 标签,并使 a 标签的内容与 href 属性相匹配即可解决问题。


令牌泄漏并不是很高,但将代码注入到我所有客户接收的营销电子邮件中就非常高了。令牌通常持续几个小时。此外,在某些情况下,获取令牌并不是那么困难。 - Bart Calixto
是的,但由于电子邮件已经有那么多的限制,所以谈论注入代码实际上只涉及到 "img" 标签和链接(至少对我而言,如果我不知道电子邮件攻击,请随意指出)。如果您不希望在其中包含链接,您可以通过用 "%{activation_link%}" 替换 "<a href="%{activation_code}>...</a>" 来禁止它们。 - Jerska
我认为在你的电子邮件中有一个“在somesite.com购买VIAGRA”的文本可能会带来潜在的高安全风险。这不一定是恶意代码。 - Bart Calixto
那么,是的,但仍然可以通过接受的答案得到相同的可能结果。 - Jerska
您不能仅使用其令牌编辑已接受答案的模板。我同意它可以替换“允许字段”,这在我看来可以大大减少影响并且可以验证字段。无论如何,出于安全原因,我不偏向任何一种方法,我只是想指出评论“如果令牌泄漏,风险已经如此之高,这是您最后关心的问题”在某些情况下是无效的。 - Bart Calixto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接