发布于 
pv: - | uv: -

邮件退订的设计与实现

何为邮件退订

在平常的验证码, 推广邮件中, 我们通常会在最下角找到 退订链接。通常访问它, 我们就不会再收到他们发送的邮件。

但是, 如何以最简单, 最节省性能的方式去实现这一功能呢?

一开始

其实最简单的方式, 貌似就是在 发送邮件时, 生成一段随机字符, 存储在数据库或缓存中(已经缓存, 可直接读取), 然后拼接成一个网址, 附在邮件底部. 用户打开后 将字符串传递给后端, 从数据库或缓存中找到这段字符串所对应的邮箱.

获取到请求对定的邮箱后, 将其存储至数据库, 后续发信时, 只需查找一次便可.

这个过程其实很简单, 有没有办法让其更加简单, 发信时不去依赖服务端的持久化存储呢?

了解jwt

其实我们可以借鉴一下 jwt (json web token) 的验证思路. jwt 是由 header, payload, signature 通过小数点间隔 组成的一段字符串.

其中 header是由 typ和alg 组成的json 经过base64得出

1
2
3
4
{
"typ": "JWT",
"alg": "HS256"
}

typ是固定的, 指出这段字符为 jwt. 而 alg 则指出了签名的生成方式. 这里使用的是 sha256

payload

payload是有效负荷, 其中存储了jwt的签发单位, 签发时间, 有效时间, 公开信息 等, 它同样也是由这些信息的json 再经过base64得出.

通常情况下, payload建议包含以下字符, 但也不是必须的:

  • iss: jwt签发者

  • sub: jwt所面向的用户

  • aud: 接收jwt的一方

  • exp: jwt的过期时间,这个过期时间必须要大于签发时间

  • nbf: 定义在什么时间之前,该jwt都是不可用的.

  • iat: jwt的签发时间

  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

如, 在此处场景下, 我们可以这样

1
2
3
4
5
{
"iss": "website",
"aud": "email@example.com",
"iat": "timestamp"
}

signature

签名用于对这串 jwt 进行合法性校验. 来判断它是否经过伪造. 进行签名我们需要一个密钥, 这个密钥只能存储在服务端, 并严格保密. 这串密钥也是通常所称的 salt.

签名方式便是header中给出的alg, 如此处 我们通过 SHA256(header + payload + salt) 生成签名.

最终jwt的格式应为 header.payload.sha256(header+payload+salt)

使用

我们只需在发送邮件时, 生成这段jwt. 发送给用户, 所有的信息仅存储在用户的邮件中, 也不用担心伪造. 我们需要做的只是保证密钥的安全.

此时当用户需要退订时, 服务器接收到这段jwt后, 需先对其进行验签, 判断其是否经过伪造. 如果通过, 接着从payload中取出用户邮箱. 将其加入不发送的名单内即可.

参考