# 安全

> Shoplazza 应用审核会查的安全实践：防范常见 Web 漏洞、TLS 加密、iframe 防护、安全处理令牌、校验 Webhook 签名。

Web 应用可能以多种方式被攻击或入侵。本文介绍每个 Shoplazza 应用都应遵循的安全实践。它们保护商家数据，其中大部分也是[应用审核](/docs/app/publish-and-operate/app-requirements/security-and-merchant-risk)的检查项。

## 防范常见 Web 漏洞

你的应用必须防范常见的 Web 安全漏洞，包括但不限于 [OWASP Top 10](https://owasp.org/www-project-top-ten/)。OWASP Top 10 是一份标准的安全意识文档，列出了 Web 应用最严重的安全风险，并附有对应的防御速查表和参考资料。

对 Shoplazza 应用来说，重点关注：

- **注入**：所有数据库访问都使用参数化查询或 ORM。来自 webhook 和 API 响应的数据同样要当作不可信输入处理，和表单输入一视同仁。
- **跨站脚本（XSS）**：商家或买家提供的内容在渲染进 HTML 前必须转义，并设置 `Content-Security-Policy` 响应头限制脚本来源。
- **身份认证缺陷**：绝不能把 URL 上的 `shop` 参数当作身份凭证。店铺域名要从校验过的 [Session Token](/docs/app/developer-tools/app-bridge/session-token) 或验证过的 OAuth 回调中读取。

如果应用审核中发现漏洞，应用会被拒绝，修复后才能重新提交。想深入学习，可以使用 [Web Security Academy](https://portswigger.net/web-security)——一个带交互实验环境的免费培训资源。

## 用 TLS 加密传输

客户端（如商家的浏览器）与应用服务器之间交换的所有数据都必须用传输层安全协议（TLS）加密，确保传输中的数据只有你的服务器能读取。应用必须通过 HTTPS 提供服务并配有效证书——自签名证书不被接受。

如果还没有 TLS 证书，可以从 [Let's Encrypt](https://letsencrypt.org/) 免费获取；任何受信任的证书颁发机构签发的证书都可以。安装后用 [SSL Checker](https://www.sslshopper.com/ssl-checker.html) 验证配置是否正确。

## 设置 iframe 防护

[嵌入式应用](/docs/app/getting-started/create-public-app/embed-in-admin)运行在 Shoplazza 后台的 iframe 内。如果你的应用可以被任意站点用 iframe 嵌套，攻击者就能在上面覆盖不可见元素，诱导已登录的商家点击他们并不想执行的操作——这就是点击劫持（clickjacking）。

防护方式是在每个返回 HTML 的响应上设置 Content Security Policy 的 `frame-ancestors` 指令，只允许当前使用你应用的店铺后台嵌套。店铺后台在各店铺自己的子域名上，所以这个值必须按请求动态设置：

```text
Content-Security-Policy: frame-ancestors https://{shopdomain}.myshoplaza.com;
```

`{shopdomain}` 要从校验过的 Session Token 或 OAuth 上下文中取，绝不能来自未验证的查询参数。

如果你的应用不嵌入后台，直接禁止一切嵌套：

```text
Content-Security-Policy: frame-ancestors 'none';
```

关于点击劫持的更多内容，参考 [OWASP Clickjacking](https://owasp.org/www-community/attacks/Clickjacking) 或 [MDN 的 `frame-ancestors` 文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)。

## 安全地生成与校验令牌

### 你的应用签发的令牌

如果应用依赖自有令牌做用户认证，令牌必须随机生成且至少有 128 位[熵](https://en.wikipedia.org/wiki/Entropy)（仅在令牌长度受硬性限制时允许 64 位）。使用语言自带的加密级随机源：

- [Python](https://docs.python.org/3/library/secrets.html)
- [Ruby](https://ruby-doc.org/stdlib-2.5.1/libdoc/securerandom/rdoc/SecureRandom.html)
- [PHP](https://www.php.net/manual/en/function.random-bytes.php)
- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
- [Go](https://pkg.go.dev/crypto/rand)
- [Java](https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html)

对公开可访问的令牌（例如出现在 URL 参数中的）：

- 过期时间不超过 7 天。
- 防止令牌被第三方泄露或收录：用 [`noindex` meta 标签](https://developers.google.com/search/docs/advanced/crawling/block-indexing)阻止搜索引擎收录，并在所有接受令牌的 URL 上返回 [`Referrer-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) 响应头，值为 `origin-when-cross-origin` 或更推荐的 `no-referrer`。

### Shoplazza 签发的令牌

后端在信任 Session Token 前必须逐个校验——用 `CLIENT_SECRET` 检查 HS256 签名并拒绝过期令牌。完整步骤和代码见 [Session Token 校验](/docs/app/developer-tools/app-bridge/session-token)。

如果应用存储自有用户凭证，只能存加盐的密码哈希，绝不能存明文密码，做法参考 [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)。另外绝不能收集 Shoplazza 账号凭证——公开应用必须通过 [OAuth](/docs/app/building-blocks/authentication/oauth-flow) 认证。

## 校验 Webhook 签名

任何知道你 webhook 地址的人都能向它发送伪造请求。处理任何 webhook 前，先校验 Shoplazza 附在每次推送上的 HMAC 签名，校验失败返回 `401`。算法和示例代码见[签名验证](/docs/app/building-blocks/authentication/signature-verification)。

应用的 shared secret 同理：它是这些消息的签名密钥，不能出现在客户端代码、公开仓库和日志里。

## 不暴露多余的网络服务

除非是应用正常运行必需的，避免把任何服务暴露到公网。MySQL、Redis、Memcached、Elasticsearch 这类服务绝不应从公网可达——把它们绑定到内网接口或放在防火墙之后。

应用审核时，Shoplazza 会用 Nmap 扫描应用主机的开放端口。如果发现预期外的端口，你需要解释端口上运行什么服务、为什么必须公开、做了哪些安全措施——或者直接关闭它们。
