草庐IT

ios - 如何针对 iCloud 验证 iCloud ID token ?

coder 2024-01-28 原文

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

如果我的服务器通过互联网收到一个带有 iCloud ID token 的请求,有没有办法验证它是由 Apple 发出的,而不是由发送方伪造的?

最佳答案

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

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

  1. 在您的 iOS 应用程序中从 Apple 获取临时设备检查 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 = "POST"
                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("Platform is not supported. Make sure you aren't running in an emulator.")
    }
    
  2. 您可以在设备检查框架中为每个应用程序的每个设备存储两位。使用 bit0 记住您已经向当前设备发送请求。首先调用 Device Check 验证端点以查看请求是否来自 iOS 应用程序——而不是例如某人的终端。接下来,使用设备检查查询端点获取当前设备的两个设备检查位。如果 bit0 为真,则假设该设备在您的请求表中已经至少有一行以给定的 iCloud 用户名哈希为关键字。如果有这样一行,这可能是一个合法的请求,因为很难猜到其他键。如果没有这样的行,则用户可能生成了一个假的 iCloud 用户哈希。但是,如果 bit0 为假,则该设备尚未在请求表中放入一行。在给定的 iCloud 用户名哈希上放置一行,并使用设备检查更新端点将此设备的 bit0 设置为 true。这是 AWS Lambda 中节点 8.10 中的一个示例,其中请求表位于 DynamoDB 中。

    端点.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: 'POST',
                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: 'POST',
                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: 'POST',
                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/

有关ios - 如何针对 iCloud 验证 iCloud ID token ?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

随机推荐