保护你的账户、促销和内容安全
Safeguard your accounts, promotions, and content
2021年6月9日
一句话判断
这集 Session 覆盖了反欺诈、促销码安全和内容保护的实用方案 —— 如果你的 app 有内购、优惠活动或付费内容,苹果提供的工具链(DeviceCheck、App Store Server Notifications V2、Receipt Validation)值得你认真评估。
这场 Session 讲了什么
Session 从三个维度讲解了 app 安全:账户保护(防欺诈登录)、促销安全(防优惠码滥用)、内容保护(防盗版和未授权访问)。
账户保护:使用 DeviceCheck API 识别设备的信任状态(不暴露设备 ID),检测同一设备是否频繁创建新账号(刷号行为)。结合 Sign in with Apple 减少密码泄露风险。
促销安全:App Store 的促销码(Offer Codes)和 App Store Server Notifications V2 帮助你追踪优惠码的使用情况。V2 版通知增加了签名验证和更详细的事务信息,可以精确追踪每一笔优惠购买。
内容保护:通过 Receipt Validation(收据验证)确保用户确实购买了内容,结合服务端验证(App Store Server API)检测退款和订阅状态变更。
值得深挖的点
DeviceCheck 的 Ephemeral Token 机制。DeviceCheck 提供两种 token:持久 token(通过 DCDevice.current.generateToken())和临时 token(iOS 14 新增)。持久 token 对应设备的长期状态,你可以在服务端设置每个设备的”信任位”(最多 2 bit)。临时 token 每次调用都不同,只能用来证明”这是一个真实的 Apple 设备”,不能关联到特定设备。防欺诈场景中,持久 token 适合标记已知的不良设备,临时 token 适合验证操作的真实性。
App Store Server Notifications V2 的签名验证。V2 通知的 payload 使用 JWS(JSON Web Signature)格式,苹果用私钥签名,你的服务端用苹果的公钥验证。这彻底解决了 V1 中通知可能被伪造的问题。公钥通过苹果的 JWKS(JSON Web Key Set)端点获取。每条通知都包含 signedDate,你可以检查时间戳防止重放攻击。
代码片段
使用 DeviceCheck 验证设备:
import DeviceCheck
func verifyDevice() {
let device = DCDevice.current
guard device.isSupported else {
print("DeviceCheck 不支持此设备")
return
}
device.generateToken { token, error in
guard let token = token else {
print("生成 token 失败: \(error!)")
return
}
// 将 token 发送到你的服务端验证
verifyTokenWithServer(token.base64EncodedString())
}
}
// 服务端:查询设备的信任状态
func queryDeviceTrust(deviceToken: String) async throws -> DeviceState {
let url = URL(string: "https://api.development.devicecheck.apple.com/v1/query_two_bits")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"device_token": deviceToken,
"transaction_id": UUID().uuidString,
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
// 发送请求并解析返回的 bit0/bit1 状态...
return try await performRequest(request)
}
验证 App Store Server Notification V2 签名:
import CryptoKit
import Foundation
func verifyNotificationV2(signedPayload: String) throws -> DecodedNotification {
let parts = signedPayload.components(separatedBy: ".")
guard parts.count == 3 else { throw Error.invalidFormat }
// 解码 header 和 payload
let headerData = Data(base64Encoded: parts[0].base64URLDecoded)!
let payloadData = Data(base64Encoded: parts[1].base64URLDecoded)!
let signatureData = Data(base64Encoded: parts[2].base64URLDecoded)!
// 从 header 获取 key ID 和算法
let header = try JSONDecoder().decode(JWSHeader.self, from: headerData)
// 从苹果 JWKS 端点获取公钥
let publicKey = try fetchApplePublicKey(keyId: header.kid)
// 验证签名
let signingInput = "\(parts[0]).\(parts[1])".data(using: .ascii)!
let isValid = verifyES256Signature(
data: signingInput,
signature: signatureData,
publicKey: publicKey
)
guard isValid else { throw Error.invalidSignature }
// 解析通知内容
return try JSONDecoder().decode(DecodedNotification.self, from: payloadData)
}
收据验证(本地验证):
import StoreKit
func validateReceipt() {
// 获取本地收据
guard let receiptURL = Bundle.main.appStoreReceiptURL,
let receiptData = try? Data(contentsOf: receiptURL) else {
print("无法读取收据")
return
}
let receiptBase64 = receiptData.base64EncodedString()
// 发送到苹果服务端验证(推荐,比本地验证更安全)
Task {
let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try JSONEncoder().encode([
"receipt-data": receiptBase64,
"password": "your_shared_secret"
])
let (data, _) = try await URLSession.shared.data(for: request)
let result = try JSONDecoder().decode(ReceiptResponse.self, from: data)
if result.status == 0 {
print("收据有效")
processValidReceipt(result)
}
}
}
最佳实践
- 用 DeviceCheck 标记欺诈设备而不是封禁 IP。IP 封禁在移动端几乎无效(NAT、代理),DeviceCheck 的设备级标记更精准。
- App Store Server Notifications V2 要做幂等处理。苹果可能重发同一条通知,你的服务端要用
originalTransactionId去重。 - 促销码发放要设总量限制和有效期。每次促销活动设置明确的上限,防止被刷号者批量消耗。
还有什么值得关注
- App Store Server API 新增了查询用户订阅状态的端点,不再依赖客户端上报。
- 促销码(Offer Codes)支持在 App Store 页面直接兑换,用户不需要复制粘贴。
- 苹果推荐使用 Xcode 的 StoreKit Testing 框架在本地测试所有购买流程。