OGeek|极客世界-中国程序员成长平台

标题: ios - 如何针对 iCloud 验证 iCloud ID token ? [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-11 19:02
标题: ios - 如何针对 iCloud 验证 iCloud ID token ?

我是 just reading关于在移动设备上使用 iCloud ID token 进行应用识别。

如果我的服务器通过 Internet 接收到带有 iCloud ID token 的请求,是否有办法验证它是由 Apple 发出的,而不是由发送方编造的?



Best Answer-推荐答案


看看Device Check Framework. “访问您的关联服务器可以在其业务逻辑中使用的每个设备、每个开发人员的数据。”在最近对 this SO thread 中的答案的评论中提出了建议。 .

这是如何使用带有 iCloud 用户 ID 哈希的设备检查来确保对您的 API 的请求是合法的。以下很多代码改编自this .

  1. 在您的 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.")
    }
    
  2. 您可以在设备检查框架中为每个设备的每个应用存储两个位。使用 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/






欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://ogeek.cn/) Powered by Discuz! X3.4