我是 just reading关于在移动设备上使用 iCloud ID token 进行应用识别。
如果我的服务器通过 Internet 接收到带有 iCloud ID token 的请求,是否有办法验证它是由 Apple 发出的,而不是由发送方编造的?
Best Answer-推荐答案 strong>
看看Device Check Framework. “访问您的关联服务器可以在其业务逻辑中使用的每个设备、每个开发人员的数据。”在最近对 this SO thread 中的答案的评论中提出了建议。 .
这是如何使用带有 iCloud 用户 ID 哈希的设备检查来确保对您的 API 的请求是合法的。以下很多代码改编自this .
在您的 iOS 应用中从 Apple 获取一个临时的 Device Check token ,然后将其与您的请求以及 iCloud 用户名哈希一起发送到您的后端。
在 Swift 4 中:
import DeviceCheck
let currDevice = DCDevice.current
if ViewController.currDevice.isSupported {
ViewController.currDevice.generateToken { (data, error) in
if let data = data {
let url = "your-url"
let sesh = URLSession(configuration: .default)
var req = URLRequest(url: url)
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpMethod = "OST"
DispatchQueue.main.sync {
var jsonObj = [
"deviceCheckToken" : data.base64EncodedString(),
"iCloudUserNameHash": self.iCloudUserID,
"moreParams": "moreParamsHere"
]
let data = try! JSONSerialization.data(withJSONObject: jsonObj, options: [])
req.httpBody = data
let task = sesh.dataTask(with: req, completionHandler: { (data, response, error) in
if let data = data, let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers), let jsonDictionary = jsonData as? [String: Any] {
DispatchQueue.main.async {
// Process response here
}
}
})
task.resume()
}
} else if let error = error {
print("Error when generating a token:", error.localizedDescription)
}
}
} else {
print("latform is not supported. Make sure you aren't running in an emulator.")
}
您可以在设备检查框架中为每个设备的每个应用存储两个位。使用 bit0 记住您已经向当前设备提供了请求。首先调用 Device Check 验证端点以查看请求是否来自 iOS 应用程序——而不是例如某人的终端。接下来,使用 Device Check 查询端点获取当前设备的两个 Device Check 位。如果 bit0 为真,则假设此设备在您的请求表中已经至少有一行键入了给定的 iCloud 用户名哈希。如果有这样的一行,这可能是一个合法的请求,因为很难猜出其他键。如果没有这样的行,用户可能生成了一个虚假的 iCloud 用户哈希。但是,如果 bit0 为 false,则该设备尚未在 requests 表中放入一行。在给定的 iCloud 用户名哈希上键入一行,并使用 Device Check 更新端点将此设备的 bit0 设置为 true。这是 AWS Lambda 中节点 8.10 中的一个示例,其中 requests 表位于 DynamoDB 中。
endpoint.js
const AWS = require('aws-sdk');
const utf8 = require('utf8');
const asyncAWS = require('./lib/awsPromiseWrappers');
const deviceCheck = require('./lib/deviceCheck');
const util = require('./lib/util');
// AWS globals
const lambda = new AWS.Lambda({
region: process.env.AWS_REGION,
});
const dynamodb = new AWS.DynamoDB.DocumentClient();
// Apple Device Check keys
const cert = utf8.encode([
process.env.BEGIN_PRIVATE_KEY,
process.env.APPLE_DEVICE_CHECK_CERT,
process.env.END_PRIVATE_KEY,
].join('\n')); // utf8 encoding and newlines are necessary for jwt to do job
const keyId = process.env.APPLE_DEVICE_CHECK_KEY_ID;
const teamId = process.env.APPLE_ITUNES_CONNECT_TEAM_ID;
// Return true if device check succeeds
const isLegitDevice = async (deviceCheckToken, iCloudUserNameHash) => {
// Pick the correct (dev or prod) Device Check API URL
var deviceCheckHost;
if (process.env.STAGE === 'dev') {
deviceCheckHost = process.env.DEV_DEVICE_CHECK_API_URL;
} else if (stage === 'prod') {
deviceCheckHost = process.env.PROD_DEVICE_CHECK_API_URL;
} else {
util.cloudwatchLog(`--> Unrecognized stage ${stage}. Aborting DC`);
return;
}
// Make sure device is valid. If not, return false
try {
await deviceCheck.validateDevice(
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
} catch (err) {
util.cloudwatchLog(`--> DC validation failed. ${err}`);
return false;
}
// Query for Device Check bits
var dcQueryResults;
try {
dcQueryResults = await deviceCheck.queryTwoBits(
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
} catch (err) {
dcQueryResults = null;
}
// If bit0 is true, then this device already has at least one row in the
// search counts table
if (dcQueryResults && dcQueryResults.bit0) {
// Try to get the counts row keyed on given user name
const getParams = {
TableName: process.env.SEARCH_COUNTS_TABLE,
Key: { u: iCloudUserNameHash },
};
var countsRow;
try {
countsRow = await asyncAWS.invokeDynamoDBGet(dynamodb, getParams);
} catch (err) {
const msg = `--> Couldn't get counts row during DC call: ${err}`;
util.cloudwatchLog(msg);
return false;
}
// If it doesn't exist, return false
if (!countsRow) {
return false;
} else { // if it DOES exist, this is a legit request
return true;
}
} else {
// Initialize the row in memory
const secsSinceEpoch = (new Date()).getTime() / 1000;
const countsRow = {
h: [0, secsSinceEpoch],
d: [0, secsSinceEpoch],
w: [0, secsSinceEpoch],
m: [0, secsSinceEpoch],
y: [0, secsSinceEpoch],
a: 0,
u: iCloudUserNameHash,
};
// Put it in the search counts table
const putParams = {
Item: countsRow,
TableName: process.env.SEARCH_COUNTS_TABLE,
};
try {
await asyncAWS.invokeDynamoDBPut(dynamodb, putParams);
} catch (err) {
const msg = `--> Couldn't set counts row in DC call: ${err}`
util.cloudwatchLog(msg);
return false;
}
// Set the device check bit
try {
await deviceCheck.updateTwoBits(true, false,
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
} catch (err) {
const msg = `--> DC update failed. ${iCloudUserNameHash} ${err}`;
util.cloudwatchLog(msg);
return false;
}
// If we got here, the request was legit
return true;
}
};
exports.main = async (event, context, callback) => {
// Handle inputs
const body = JSON.parse(event.body);
const iCloudUserNameHash = body.iCloudUserNameHash;
const deviceCheckToken = body.deviceCheckToken;
const otherParams = body.otherParams;
// If allowed to search, increment search counts then search
var deviceCheckSucceeded;
try {
deviceCheckSucceeded =
await isLegitDevice(deviceCheckToken, iCloudUserNameHash);
} catch (err) {
util.cloudwatchLog(`--> Error checking device: ${err}`);
return callback(null, resp.failure({}));
}
if (deviceCheckSucceeded) {
// Do your stuff here
return callback(null, resp.success({}));
} else {
return callback(null, resp.failure({}));
}
};
deviceCheck.js
const https = require('https');
const jwt = require('jsonwebtoken');
const uuidv4 = require('uuid/v4');
const util = require('../lib/util');
// Set the two Device Check bits for this device.
// Params:
// bit0 (boolean) - true if never seen given iCloud user ID
// bit1 (boolean) - TODO not used yet
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
const updateTwoBits = async (
bit0, bit1, cert, keyId, teamId, dcToken, deviceCheckHost) => {
return new Promise((resolve, reject) => {
var jwToken = jwt.sign({}, cert, {
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
});
var postData = {
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
'bit0': bit0,
'bit1': bit1,
}
var postOptions = {
host: deviceCheckHost,
port: '443',
path: '/v1/update_two_bits',
method: 'OST',
headers: {
'Authorization': 'Bearer ' + jwToken,
},
};
var postReq = https.request(postOptions, function(res) {
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
util.cloudwatchLog(
`--> Update bits done with status code ${res.statusCode}`);
resolve();
});
res.on('error', function(data) {
util.cloudwatchLog(
`--> Error ${res.statusCode} in update bits: ${data}`);
reject();
});
});
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
});
};
// Query the two Device Check bits for this device.
// Params:
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
// Return:
// { bit0 (boolean), bit1 (boolean), lastUpdated (String) }
const queryTwoBits = async (cert, keyId, teamId, dcToken, deviceCheckHost) => {
return new Promise((resolve, reject) => {
var jwToken = jwt.sign({}, cert, {
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
});
var postData = {
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
}
var postOptions = {
host: deviceCheckHost,
port: '443',
path: '/v1/query_two_bits',
method: 'OST',
headers: {
'Authorization': 'Bearer ' + jwToken,
},
};
var postReq = https.request(postOptions, function(res) {
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
try {
var json = JSON.parse(data);
resolve({
bit0: json.bit0,
bit1: json.bit1,
lastUpdated: json.last_update_time,
});
} catch (e) {
const rc = res.statusCode;
util.cloudwatchLog(
`--> DC query call failed. ${e}, ${data}, ${rc}`);
reject();
}
});
res.on('error', function(data) {
const code = res.statusCode;
util.cloudwatchLog(
`--> Error ${code} with query bits call: ${data}`);
reject();
});
});
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
});
};
// Make sure devie is valid.
// Params:
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
const validateDevice = async (
cert, keyId, teamId, dcToken, deviceCheckHost) => {
return new Promise((resolve, reject) => {
var jwToken = jwt.sign({}, cert, {
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
});
var postData = {
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
}
var postOptions = {
host: deviceCheckHost,
port: '443',
path: '/v1/validate_device_token',
method: 'OST',
headers: {
'Authorization': 'Bearer ' + jwToken,
},
};
var postReq = https.request(postOptions, function(res) {
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
util.cloudwatchLog(
`--> DC validation done w/ status code ${res.statusCode}`);
if (res.statusCode === 200) {
resolve();
} else {
reject();
}
});
res.on('error', function(data) {
util.cloudwatchLog(
`--> Error ${res.statusCode} in DC validate: ${data}`);
reject();
});
});
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
});
};
exports.updateTwoBits = updateTwoBits;
exports.queryTwoBits = queryTwoBits;
exports.validateDevice = validateDevice;
关于ios - 如何针对 iCloud 验证 iCloud ID token ?,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/46820967/
|