转载

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

系列内容:

此内容是该系列 3 部分中的第 # 部分: 设计和构建安全的 IoT 解决方案,第 3 部分

http://www.ibm.com/developerworks/cn/views/iot/libraryview.jsp?search_by=%E8%AE%BE%E8%AE%A1%E5%92%8C%E6%9E%84%E5%BB%BA%E5%AE%89%E5%85%A8%E7%9A%84+IoT+%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88

敬请期待该系列的后续内容。

此内容是该系列的一部分: 设计和构建安全的 IoT 解决方案,第 3 部分

敬请期待该系列的后续内容。

基于云的 IoT 应用程序中安全的一个重要目标是确保未授权的用户无法访问来自设备的敏感和隐私数据。该应用程序还需要防范向设备发送未授权的命令。本文(一个由三部分组成的文章系列的第 3 部分)将介绍保护 Web 和移动应用程序处理 IoT 设备数据的不同方法。第 1 部分和第 2 部分列出了保护设备,以及保护设备与网络之间通信的详细方法。

图 1 展示了本文中所介绍的方法。移动和 Web 客户端具有特定的安全机制来确保只有经过验证的用户才能访问应用程序。应用程序逻辑的核心在基于 Bluemix 的后端服务中。此应用程序逻辑从 IBM Watson IoT Platform 拉取数据(通过使用受保护的 Watson IoT Platform API),分析数据,然后发送命令来控制设备。

图 1. 保护处理 IoT 设备数据的 Web 和移动应用程序的方法

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

本文假设您知道如何开发和部署 Bluemix 应用程序。如果您还不熟悉 Bluemix,可以考虑学习 developerWorks Bluemix 基础学习路径 中的教程。本文不会提供如何开发 Web 或移动应用程序的详细说明;只解释如何将安全特性添加到 Web 或移动应用程序中去。

保护应用程序所收到的 IoT 数据

我们将为本文创建的 IoT 安全演示服务器应用程序订阅 Watson IoT 事件数据,并将收到的数据持久保存在 Cloudant 数据库中。在持久保存数据(比如来自 IoT 设备的有效负载)时,IoT 安全演示服务器应用程序会对所存储数据的一些敏感属性进行加密。有效负载为 JSON 格式并以 AES 加密格式存储在 Cloudant 数据库中。它会在被授权用户获取时解密。加密和解密对用户是透明的。

获取 IoT 安全演示服务器应用程序的代码

针对 Watson IoT Platform 的受保护访问

在 Bluemix 中添加 Internet of Things Platform 服务时,会为您的应用程序生成访问凭证(比如下面所示的凭证),而这些凭证会授予您访问 IBM Watson IoT Platform 的权限。

{ 
   "iotf-service":[ 
      { 
         "name":"your-iotf-service-name",
         "label":"iotf-service",
         "tags":[ 
            "internet_of_things",
            "Internet of Things",
            "ibm_created",
            "ibm_dedicated_public"
         ],
         "plan":"iotf-service-free",
         "credentials":{ 
            "iotCredentialsIdentifier":"your-credentials",
            "mqtt_host":"your-org-name.messaging.internetofthings.ibmcloud.com",
            "mqtt_u_port":1883,
            "mqtt_s_port":8883,
            "base_uri":"https://your-org-name.internetofthings.ibmcloud.com:443/api/v0001",
            "http_host":" your-org-name.internetofthings.ibmcloud.com",
            "org":" your-org-name ",
            "apiKey":"a-your-org-name-your-key",
            "apiToken":"your-token"
         }
      }
   ]
}

订阅 Watson IoT Platform 的事件和状态更新很简单,如下面代码片段所示:

var IotfClient = require("ibmiotf").IotfApplication;
 
var iotConfig = {
        "org" : org,
        "id" : "iotsecuritydemo",
        "auth-key" : apiKey,
        "auth-token" : apiToken
    };
 
var iotfClient = new IotfClient(iotConfig);
 
iotfClient.connect();
 
iotfClient.on("error", function (err) {
    console.log("IoTF client error: "+JSON.stringify(err, null, 4));
});
 
iotfClient.on("connect", function () {
    // Subscribe to status from all devices
    iotfClient.subscribeToDeviceStatus();
         
    // Subscribe to all events from all devices
        iotfClient.subscribeToDeviceEvents();
    });
 
iotfClient.on("deviceEvent", function (deviceType, deviceId, eventType, format, payload) {
    // Handle events from devices
      console.log("Device event from:"+deviceType+", "+deviceId+" of event "+eventType);
     
      insertEventToDB(deviceType, deviceId, eventType, format, payload);
});
 
iotfClient.on("deviceStatus", function (deviceType, deviceId, payload, topic) {
    // Handle status updates from devices
      console.log("Device status from:"+deviceType+", "+deviceId);
     
      insertStatusToDB(deviceType, deviceId, payload, topic);
});

通过此代码,无论何时收到 IoT 事件,事件和相关有效负载就会插入 Cloudant 数据库中。

加密 Cloudant 数据库中的数据

专用的 Cloudant 实例支持在将数据存储在数据库时对数据属性进行加密,但特定于 Bluemmix 的 Cloudant 服务目前还不支持加密。然而,您可以实现一种自定义加密方法来确保敏感设备数据受到保护。

对存储和检索的数据的加密和解密可以基于行业标准的 AES-256 算法,如以下代码段所示:

var crypto = require('crypto');
var algorithm = 'aes-256-ctr';
var cryptoKey = 'your-key';
 
function encrypt(text){
  var cipher = crypto.createCipher(algorithm, cryptoKey);
  var crypted = cipher.update(text,'utf8','hex');
  crypted += cipher.final('hex');
  return crypted;
}
  
function decrypt(text){
  var decipher = crypto.createDecipher(algorithm, cryptoKey);
  var dec = decipher.update(text,'hex','utf8');
  dec += decipher.final('utf8');
  return dec;
}

加密数据的同时会带来性能成本,尤其是在需要处理大量数据时,所以在考虑和实现正确的方法时必须考虑这方面。

安全地访问存储在您的 Cloudant 数据库中的数据

在 IBM Cloudant 文档中进一步了解 Cloudant 数据保护和安全性 。

您可以通过以下两种方法中的一种来直接访问存储在 Cloudant 数据库中的 IoT 数据:

  • 如果共享了 Cloudant API 密钥,则使用第三方应用程序,
  • 创建您自己的受 Bluemix 安全服务保护的自定义 API。

使用 Cloudant API 密钥的第三方访问

在 Bluemix 中添加 Cloudant DB 服务时,会为您的应用程序生成类似以下这样的访问凭证,以便让您的程序能够以编程方式访问 Cloudant 数据库。

{
  "cloudantNoSQLDB": [
    {
      "name": "Your name",
      "label": "cloudantNoSQLDB",
      "plan": "Shared",
      "credentials": {
        "username": "Your user id",
        "password": "Your password",
        "host": "Your host id",
        "port": 443,
        "url": "https://username:password@.cloudant.com"
      }
    }
  ]
}

可以使用此用户名和密码生成 Cloudant API 密钥,方法如下:

POST _api/v2/api_keys

生成密钥后,可像正常用户帐户一样使用 API 密钥,比如授予读取、写入或管理访问权。

POST https://<username:password>.cloudant.com/_api/v2/api_keys

响应将类似于以下代码:

"password": "YPNCaIX1sJRX5upaL3eqvTfi",
"ok": true,
"key": "blentfortedsionstrindigl"

需要 API 密钥才能授予访问特定数据库的权限。使用如下所示的 PUT 请求访问您的数据库。

https://<username>.cloudant.com/_api/v2/db/<database>/_security),

请注意,也可以从 Cloudant 仪表板生成 API 密钥,如下图所示。

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

API 密钥/密码对可以像其他任何用户帐户一样对待。您可以将 API 密钥传递给一个调用,以便使用它共享数据库,并向它分配权限。可以将 API 密钥/密码对用于任何不适合用户帐户的情形,比如在您的源代码中包含凭证时,以及需要以编程方式创建多个具有不同权限的帐户时。

API 响应包含以下属性:

  • ok - 在请求成功时返回。
  • error - 在请求未成功时返回,包含错误代码。
  • key - 返回 API 密钥。
  • password - 返回 API 密码。

使用自定义 API 的第三方访问

通过提供对 Cloudant 数据库的直接访问能力,会让第三方集成变得非常脆弱且维护成本高昂。一种推荐的架构是使用自定义 API 在数据库上提供一个封装层。自定义 API 将会隐藏特定于数据库的细节,并返回来自特定接口契约的设备信息。

我们的 IoT 安全演示服务器应用程序中的第一个自定义 API 允许您使用这个 http 请求访问设备列表:

http://<app_route>/iotf/devices

例如,要访问存储在我们的 IoT 安全演示服务器应用程序中的 Cloudant 数据库中的设备列表的数据,可以使用以下请求:

http://iotsecuritydemo.mybluemix.net/iotf/devices

我们的 IoT 安全演示服务器应用程序中的第二个自定义 API 允许您使用这个 http 请求访问特定设备的数据:

http://<app_route>/iotf/devices/<device>

例如,要查看我们的 IoT 安全演示服务器应用程序中的最后 50 个事件及其针对设备“j.patra”的有效负载,可使用以下请求:

http://iotsecuritydemo.mybluemix.net/iotf/devices/j.patra

最后,您可以通过在请求上指定一个额外的参数来请求特定数量的事件:

http://<app_route>/iotf/devices/<device>?count=<count>

例如,要查看我们的 IoT 安全演示服务器应用程序中的最后 5 个事件及其针对设备“j.patra”的有效负载,可使用以下请求:

http://iotsecuritydemo.mybluemix.net/iotf/devices/j.patra?count=5

使用 Mobile Client Access 保护 API

通过 Bluemix 文档进一步了解如何 开始使用 Mobile Client Access 。

数据受到保护并通过自定义 API 公开后,下一步是使用身份验证和授权来保护这些 API。Web 和移动应用程序需要提供必要的身份验证凭证来调用这些 API,并且只能访问它们有权访问的设备的数据。为了保护自定义 API,本文使用了 IBM Bluemix 上的 Mobile Client Access 服务。

本文中的 API 是使用 Node.js 开发的。为了保护 API 端点,通过通行证身份验证中间件(passport authentication middleware)对 Node.js 中的 API handler 方法进行了增强。密码身份验证中间件是一个 Node.js 通行证(passport)实例,它已使用 Mobile Client Access 策略对象初始化,以便身份验证请求被 Mobile Client Access 服务器 SDK 拦截和处理。

实现 API 安全性

下面这段代码片段来自 IoT 安全演示服务器应用程序,它演示了如何结合使用 Mobile Client Access 和通行证身份验证中间件来保护自定义 API 端点(/iotf/devices)。

// --------- Protecting mobile back end with Mobile Client Access -----------
 
// Load passport (http://passportjs.org)
var passportMCA = require('passport');
 
// Get the MCA passport strategy to use
var MCABackendStrategy = require('bms-mca-token-validation-strategy').MCABackendStrategy;
 
// Tell passport to use the MCA strategy
passportMCA.use(new MCABackendStrategy());
 
// Tell application to use passport
app.use(passportMCA.initialize());
 
// The serialize-deserialize functions save the user object in the session
passportMCA.serializeUser(function(user, done) { 
    done(null, user); 
}); 
passportMCA.deserializeUser(function(obj, done) { 
    done(null, obj); 
}); 
 
var iotRouteBase = '/iotf';
 
// This is a protected API
app.get(iotRouteBase + '/devices', 
        passportMCA.authenticate('mca-backend-strategy', {session: true}), 
        function(req, res) {
            // Check for the user group and filter out the devices that the user does not have access to
            executeIfAllowed(req.user, '', 'reader', 
                function(error, userGroup) {
                    if(error) {
                        res.status(400).json(error);
                    }
                    else {
                        getDeviceList(req.user, userGroup, 
                            function(error, result) {
                                if(error) {
                                    res.status(400).json(error);
                                }
                                else {
                                    res.status(200).send(result);
                                }
                          });
                     }
                });
        }
);

useruserGroup 对象是自定义的身份验证和授权对象,它们对 Cloudant 数据库中存储的数据提供基于角色的安全访问。本文使用一个自定义身份验证提供程序(custom authentication provider)来使用这些 useruserGroup 对象。

实现自定义身份提供程序(custom identity provider)

请参阅 Bluemix 文档 ,了解设计自定义身份提供程序(custom identity provider)的最佳实践,包括是否需要支持提供程序中的状态信息,在支持双因素身份验证等特定身份验证场景中,可能需要这么做。

使用自定义 API 时,您可能希望更多地控制访问设备的数据,并向设备发送命令的用户授权。要获得此控制权,可在 Cloudant 中为 useruserGroup 创建一组自定义文档。

Cloudant 中的这组自定义文档用于为此 IoT 安全演示服务器应用程序中实现的自定义身份验证提供程序(custom authentication provider)执行基于角色的授权。

自定义 user 文档的示例:

{
  "_id": "joypatra@gmail.com",
  "recordType": "user",
  "userName": "joypatra",
  "password": "encrypted-password",
  "displayName": "Joy Patra",
  "attributes": {
    "Language": "English",
    "Country": "India",
    "emailAddress": "joypatra@gmail.com",
    "userGroup": "groupAdmin"
  }
}

自定义 userGroup 文档的示例:

{
  "_id": "groupViewers",
  "recordType": "userGroup",
  "roleAdmin": "false",
  "roleReader": "true",
  "roleWriter": "false",
  "displayName": "Device administrator group",
  "devices": [“j.patra”, “m.patra”]
}

本文中开发的自定义身份提供程序使用了来自 Cloudant 数据库的上述 useruserGroup 文档。它在 IBM Bluemix 上的 Node.js 后端服务器上运行,提供了 Mobile Client Access 服务验证用户所需的两个 RESTful API。Mobile Client Access 服务旨在在任何自定义身份验证提供程序中调用这两个 API。

以下两个 API 由 Mobile Client Access 服务预先确定: startAuthorizationhandleChallengeAnswer 。Mobile Client Access 服务要求这两个 RESTful API 具有以下格式:

POST <base_url>/apps/<tenant_id>/<realm_name>/<request_type>

代码 描述
base_url 指定自定义身份提供程序 Web 应用程序的基础 URL(base URL)。该基础 URL 是要在 Mobile Client Access 仪表板中注册的 URL。
tenant_id 指定租户的唯一标识符。当 Mobile Client Access 调用此 API 时,它始终会提供 IBM Bluemix 应用程序 GUID。
realm_name 指定在 Mobile Client Access 仪表板中定义的自定义范围名称。
request_type 指定以下请求类型之一:
  • startAuthorization: 指定身份验证流程的第一步骤。自定义身份提供程序必须使用“challenge”(质询)、“success”(成功)或“failure”(失败)状态来响应。
  • handleChallengeAnswer: 处理来自移动客户端的身份验证质询响应。
请参阅 IBM Bluemix 文档 ,了解这些请求类型和数据格式的更多细节。

本文中实现的自定义身份验证提供程序实现了这两种要求的方法。下面的代码段显示了针对 Cloudant 数据库的用户验证。

app.post('/apps/:tenantId/:realmName/startAuthorization', jsonParser, function(req, res){
	var tenantId = req.params.tenantId;
	var realmName = req.params.realmName;
	var headers = req.body.headers;

	var response = { status: "failure" };

	var validTenant = isValidTenant(tenantId);
	var validRealm = isValidRealm(realmName);
	
	if(validTenant && validRealm) {
		response = {
				status: "challenge",
				challenge: { text: "Enter username and password" }
			};
		console.log("Responding with challenge.");
		res.status(200).json(response);
	}
	else {
		response.failure = { text: "Wrong tenant id or realm name." };
		console.log("Responding with failure.”);
		res.status(400).json(response);
	}
});

app.post('/apps/:tenantId/:realmName/handleChallengeAnswer', jsonParser, function(req, res){
	var tenantId = req.params.tenantId;
	var realmName = req.params.realmName;
	var challengeAnswer = req.body.challengeAnswer;

	var username = req.body.challengeAnswer["username"];
	var password = req.body.challengeAnswer["password"];

	var response = { status: "failure" };

	var validTenant = isValidTenant(tenantId);
	var validRealm = isValidRealm(realmName);
	
	if(validTenant && validRealm) {
		// Retrieve custom user document from Cloudant db
		db.get(username, function(error, userIdentity) {
			var response = { status: "failure" };
			
			if(! error) {
				if(encrypt(password) == userIdentity.password) {
					response.status = "success";
					response.userIdentity = userIdentity;
				}
				else
					error = new Error("Username or password does not exist or does not match.");
			}

			response.failure = error;

			if(response.status == "success") {
				console.log("Responding with success.");
				res.status(200).json(response);
			}
			else {
				console.log("Responding with failure.");
				res.status(400).json(response);
			}
		});
	}
	else {
		response.failure = { text: "Wrong tenant id or realm name." };
		console.log("Responding with failure.");		    
            res.status(400).json(response);
	}
});

最后,对于自定义身份提供程序,您需要开发自定义身份验证质询(challenge)来发送给客户端。身份验证质询是一个 JSON 对象,如下面的代码段所示。您可以将自己的自定义字段放入质询属性中,如示例所示。客户端应用程序会收到这些自定义字段,并恰当地选择使用它们。

{
    status: "challenge",
    challenge: {
        message: "Enter username and password",
        customField_retriesLeft: 2,
        customField_minUsernameLength: 8
    }
}

前端应用程序采用以下格式发送对质询的回复。它可将自定义属性嵌入到回复中。服务器端应用程序将收到这些自定义属性,而且可以选择使用它们。

{
    username: "joypatra@gmail.com",
    password: "your-password",
    otherCustomField: "1234"
}

本文中在服务器端上实现的自定义身份提供程序使用了 Cloudant 数据库中的自定义 useruserGroup 文档来验证回复中所提供的凭证。

当用户验证成功时,服务器端的自定义身份提供程序会将 Cloudant 中的 user 文档返回给 Mobile Client Access 服务。Mobile Client Access 服务通过从这个 user 文档复制某些属性来创建会话用户对象。下面的代码段显示了 Mobile Client Access 服务创建的一个 user 对象示例。

{
    "id": "joypatra",
    "authBy": "iotsecuritydemoRealm",
    "displayName": "Joy Patra",
    "attributes": {
	"userGroup":"groupAdmin",
	"emailAddress":"joypatra@gmail.com",
	"Language":"English",
	"Country":"India"
    }
}

Mobile Client Access 服务还将 attributes 属性“按原样”从 user 文档复制到 session user 对象。因此,您可以使用 attributes 属性来包含所有特定于应用程序的自定义属性。

访问受保护的 API

客户端应用程序可以通过以下两种方法中的一种来访问这些受保护的自定义 API:

  • 后端到后端的集成策略
  • 基于 API 的集成策略。

表 1 比较了这两种方法。

表 1. 客户端应用程序访问受保护的自定义 API 的两种方法

后端到后端的集成策略 基于 API 的集成策略
目标后端应用程序需要共享“机密”密钥值(secret key value),此策略方可适用于前端应用程序。来自前端应用程序且在请求标头中包含这个“机密”密钥的所有请求,都会被后端应用程序 API 接受,而无需执行任何进一步的身份验证。 前端应用程序不需要知道“机密”。前端应用程序将用户凭证传递给后端应用程序 API,以便后端应用程序可对它进行身份验证和授权。
针对于用户的身份验证和授权的责任由前端应用程序负责。只要前端传递了正确的“机密”值,后端就会直接信任前端。 前端应用程序不执行身份验证或授权。
集成非常简单。 前端应用程序需要实现自定义身份验证监听器,以便在使用 Mobile Client Access 自定义身份验证提供程序时处理 Mobile Client Access 质询。
截至编写本文时,没有显式的“注销”方法。 截至编写本文时,没有显式的“注销”方法。

实施后端到后端的集成策略

在 Mobile Client Access 后端到后端集成策略中,安全凭证(secret 字段)是在 http 请求的授权标头中传递的,以获取所需的数据访问权。下面显示了调用该 API 的示例代码。

var oauthSDK = require('bms-mca-oauth-sdk');
var request = require('request');
 
var mca_options = {
        cacheSize: 100,
        appId: "Your app id",                
        clientId: "Your client id",            
        secret: "Secret",
        serverUrl: "https://imf-authserver.ng.bluemix.net/imf-authserver"
    };
 
app.get('/devices', ensureAuthenticated, function(req, res) {
    var user_email = req.user._json.emailAddress;
    console.log("req.user - " + user_email);
    oauthSDK.getAuthorizationHeader(mca_options).then(function(authHeader){
        req.headers.Authorization = authHeader;
        request({
            url: 'http://iotsecuritydemo.mybluemix.net/iotf/devices', //URL to hit
            method: 'GET', //Specify the method, a POST is recommended
            headers: { //We can define headers too
                'Authorization': authHeader
            }
        }, function(error, response, body){
            if(error) {
                console.log(error);
            } else {
                var html = "";
                html += "<p>IOTF Devices:</p>"
                html += "<pre>" + body + "</pre>";
                res.send(html);
            }
        });
    });
});

实施基于 API 的集成策略

在基于 API 的集成策略中,前端应用程序只能通过提供特定于用户的必需凭证来获取对后端的访问权。对于 Facebook 或 Google 身份验证,Mobile Client Access 客户端 SDK 在前端处理与 Facebook 或 Google 身份验证库的交互。对于自定义身份验证提供程序,前端应用程序需要实现一个身份验证监听器。

例如,您可以按照以下方式针对 Cordova 而初始化 Mobile Client Access 客户端 SDK:

BMSClient.initialize(applicationRoute, applicationGUID);

applicationRouteapplicationGUID 是托管在 Bluemix 上的后端应用程序的 Bluemix Application Route 和 Bluemix Application GUID。

使用来自 Mobile Client Access Bluemix 文档 中关于自定义身份验证监听器(基于 Cordova)的示例实现。

在创建自定义身份验证监听器后,向 BMSClient 注册该监听器后才能使用它。必须先调用监听器,然后您才能对受保护的后端 API 发起请求,如以下代码所示:

BMSClient.registerAuthenticationListener(realmName, customAuthenticationListener);

realmName 是您在 Bluemix 中的 Mobile Client Access 仪表板中配置自定义身份验证提供程序时指定的名称。

保护 IoT 客户端应用程序

在 Bluemix 文档 中进一步了解 Bluemix 安全性。

要保护 IoT 客户端应用程序,可以对 Web 和移动应用程序采用不同的身份验证,或者对 Web 和移动应用程序使用统一的身份验证。

开发人员可根据他们所开发的应用程序类型来选择上述方式的其中一种。Bluemix 云平台在平台、数据和应用程序级别上都提供了安全性。

选项 1:为 Web 和移动应用程序执行不同的身份验证

如果使用不同的身份验证方式,Web 应用程序会使用 Bluemix SSO 服务(基于 Cloud Directory)执行用户身份验证,而移动应用程序会使用 MobileFirst Client Access 服务(基于自定义身份验证提供程序)来验证用户。但是,Web 和移动应用程序都使用相同的受保护应用层向用户授予设备数据的访问权。参见 图 2。

图 2. 不同的身份验证,相同的受保护应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

选项 2:为移动和 Web 应用程序执行统一的身份验证

图 3 展示了基于 Mobile Client Access 服务的统一身份验证和授权方式。借助这种统一的身份验证方式,移动应用程序和 Web 应用程序都使用同一个基于 Cloudant 的自定义身份验证和授权提供程序,以保护设备数据和限制谁可以向设备发送命令。

图 3. 相同的身份验证,相同的受保护应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

保护 Web 应用程序

在我们的 IoT SSO(单点登录)和授权演示 Web 应用程序中,我们使用了 IBM Cloud Directory(IBM 云目录)来支持 Web 应用程序的用户身份验证,如选项 1:为 Web 和移动应用程序执行不同的身份验证所示。使用此选项,示例 Web 应用程序必须维护所需的凭证(与登录用户凭证相对应),然后才能调用后端 API。(如果希望对移动和 Web 应用程序使用相同的凭证来避免此配置,可以使用选项 2:为移动和 Web 应用程序执行统一的身份验证。)

获取 IoT SSO 和授权演示应用程序的代码

使用 IBM Cloud Directory 执行身份验证

提供 IoT 设备数据访问权的 Web 应用程序需要使用应用程序用户的用户名/密码组合进行保护。在我们的 Web 应用程序中,后端用户注册表(比如 IBM 云目录,IBM Cloud Directory)用于存储所有用户信息。单点登录(Single Sign-On,SSO)功能可通过使用 Bluemix SSO 服务 添加到应用程序中。

SSO 服务支持三种可存储用户凭证的身份来源:

  • SAML Enterprise :一个通过交换 SAML 令牌来完成身份验证的用户注册表。
  • Cloud Directory :一个托管在 IBM Cloud 中的用户注册表。我们在本文的示例中使用了它。
  • 社交身份来源 :由 Google、Facebook 和 LinkedIn 维护的用户注册表。

在应用程序开发人员将 SSO 功能嵌入应用程序中之前,管理员必须创建服务实例和添加身份来源。执行以下步骤将 SSO 功能(通过使用 IBM 云目录)添加到 Web 应用程序。

  1. 在 Bluemix 中设置 SSO 服务,并创建一个新的 IBM 云目录。在一个 CSV 文件中创建用户,并将它上传到云目录。参见下图: 设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序
    设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序
  2. 结合使用 Node.js passport 包 和 passport-idaas-openidconnect 包 ,从部署在 Bluemix 环境中的 Node.js 应用程序调用 SSO 服务。
    var express = require('express');
    var cookieParser = require('cookie-parser');
    var session = require('express-session');
    var passport = require('passport');
     
    var app = express();
     
    app.use(logger('dev'));
    app.use(cookieParser());
    app.use(session({
        secret : 'iotfCloud123456789',
        saveUninitialized : true,
        resave : true
    }));
    app.use(passport.initialize());
    app.use(passport.session());
     
    passport.serializeUser(function(user, done) {
        done(null, user);
    });
    passport.deserializeUser(function(obj, done) {
        done(null, obj);
    });
     
    var services = JSON.parse(process.env.VCAP_SERVICES || "{}");
    var ssoConfig = services.SingleSignOn[0];
    var client_id = ssoConfig.credentials.clientId;
    var client_secret = ssoConfig.credentials.secret;
    var authorization_url = ssoConfig.credentials.authorizationEndpointUrl;
    var token_url = ssoConfig.credentials.tokenEndpointUrl;
    var issuer_id = ssoConfig.credentials.issuerIdentifier;
    var callback_url = "https://IOTSSOAyan.mybluemix.net/auth/sso/callback";
     
    var OpenIDConnectStrategy = require('passport-idaas-openidconnect').IDaaSOIDCStrategy;
    var Strategy = new OpenIDConnectStrategy({
        authorizationURL : authorization_url,
        tokenURL : token_url,
        clientID : client_id,
        scope : 'openid',
        response_type : 'code',
        clientSecret : client_secret,
        callbackURL : callback_url,
        skipUserProfile : 'false',
        issuer : issuer_id
    }, function(iss, sub, profile, accessToken, refreshToken, params, done) {
        process.nextTick(function() {
            profile.accessToken = accessToken;
            profile.refreshToken = refreshToken;
            done(null, profile);
        })
    });
  3. 使用函数 ensureAuthenticated() 验证每次调用中的登录。
    function ensureAuthenticated(req, res, next) {
        if (!req.isAuthenticated()) {
            req.session.originalUrl = req.originalUrl;
            res.redirect('/login');
        } else {
            return next();
        }
    }

保护移动应用程序

除了保护 Web 应用程序之外,IBM Bluemix 也同样提供了服务来确保移动应用程序的安全性。这些服务中的关键是 Mobile Client Access 服务,该服务控制移动设备对应用程序的访问。

下图展示了一个在访问 IoT 数据时使用 Mobile Client Access 的受保护移动应用程序的流程(对于从自定义 Cloudant 数据库验证用户凭证的场景)。该图后面的列表描述了该图中的每一步。

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

设计和构建安全的 IoT 解决方案,第 3 部分: 保护 IoT 应用程序

  1. 一个移动设备使用了 Mobile Client Access 客户端 SDK,通过提供移动应用程序后端的 Bluemix Application Route 和 Bluemix Application GUID 来初始化与移动应用程序后端的会话。
  2. 移动设备调用一个在移动应用程序后端中受保护的端点(在我们的安全演示服务器应用程序中为 /iotf/devices)。通过插入 Node.js 通行证身份验证来保护服务器端上的端点,该身份验证方式采用了来自 Mobile Client Access 服务器端 SDK 的 Mobile Client Access 策略。
  3. Mobile Client Access 服务器端 SDK 拦截对端点上的请求,并验证是否已存在适用于此会话的 Mobile Client Access OAuth 令牌。
  4. 如果有效,Mobile Client Access 服务器端 SDK 将会允许端点处理程序被调用。否则,它会返回一个包含 HTTP 401 的响应。
  5. Mobile Client Access 客户端 SDK 知道如何拦截 Mobile Client Access 服务器端 SDK 返回的 HTTP 401 响应,客户端 SDK 触发身份验证流。
  6. 身份验证流考查是否已为 Mobile Client Access 服务配置了 Facebook、Google 或自定义身份验证提供程序。它将结果返回给 Mobile Client Access 服务器端 SDK。
  7. 针对 Mobile Client Access 服务器端 SDK 的身份验证流获得成功时,它会调用受保护的端点(在我们的安全演示服务器应用程序中为 /iotf/devices)的端点处理程序。

初始化 Mobile Client Access 客户端 SDK(适用于 Android)

您需要通过将上下文、应用程序 GUID 和应用程序路由参数传递给 initialize() 方法来初始化该 SDK,如下面的代码片段所示。

您可以在 Bluemix 应用程序仪表上的 Mobile Options 部分找到您的 Application Route 和 Application GUID。

try {
    // Initialize SDK with IBM Bluemix application ID and route
    //
    // You can find your Application Route and Application GUID in the 
    // Mobile Options section on top of your Bluemix application dashboard
 
            BMSClient.getInstance().initialize(
                    getApplicationContext(),
                    DeviceIoTDemoApplication.APPLICATION_ROUTE,
                    DeviceIoTDemoApplication.APPLICATION_ID);
     } catch (MalformedURLException e) {
            Log.e(TAG, "Error initializing Bluemix Mobile Service Client: "+e);
     }

向受保护的 API 发出请求

在 Mobile Client Access 客户端 SDK 被初始化之后,您可以向受保护的 API 发出请求:

http://&lt;app-route&gt;/iotf/devices

示例 Android 应用程序已配备了 Mobile Client Access 客户端 SDK。要从移动应用程序向上述端点发出请求,可在您初始化 BMSClient 实例后添加以下代码:

Request request = new Request(                      
           BMSClient.getInstance().getBluemixAppRoute()+"/iotf/devices", 
           Request.GET);
 
// BMSResponseListener is a custom class that has implemented ResponseListener
request.send(OAuthLoginActivity.this, new BMSResponseListener() {
    @Override
    public void onSuccess(final Response response) {
        super.onSuccess(response);
        runOnUiThread(
            new Runnable() {
               @Override
               public void run() {
                   Log.i(TAG, "Success Response=" +            
                     response.getResponseText());
                   // Execute tasks related to your Android UI
               }
            }
        );
    }
});

移动 Android 客户端示例

我们为本文开发的移动 Android 客户端示例应用程序,首先显示一个登录屏幕,在该屏幕中收集用户名、密码和要建立连接的服务器的信息。然后,调用本文前面描述的设备 API。在服务器端应用程序以身份验证质询作为响应时,客户端会提供它收集的用户名和密码。

获取示例移动 Android 客户端的代码

对于我们的安全演示服务器应用程序,这个 Android 应用程序会将用户名和密码保存在本地 Android 存储中。您可能想在实际生产应用程序中采用不同的行为。

成功执行身份验证后,设备 API 将会返回此用户有权查看的设备列表。每个设备显示在应用程序 UI 中的一个分立的选项卡中。每个选项卡显示相应设备的最后一个事件,并显示事件的时间戳和传感器有效负载数据。

该 Android 应用程序实现了一个自定义身份验证监听器。对于本文,移动应用程序将会收集用户名和密码,然后使用该用户名和密码来实例化 CustomAuthenticationListener 类。不过,在您的实现中,您可能希望选择在从 Mobile Client Access 客户端 SDK 收到身份验证质询后获取用户名和密码,我们将在后面代码中的代码注释里提到这一点。

此外,您应当捕获您代码里的 onAuthenticationChallengeReceived() 方法中出现的异常,并向 AuthenticationContext 报告发生了失败。如果没有捕获出现的异常,Mobile Client Access 客户端 SDK 将永远保持“等待凭证”状态,我们将在下面的代码中的代码注释中提到这一点。

public class CustomAuthenticationListener implements AuthenticationListener {
    private final String TAG = getClass().getSimpleName();

    private Context context;
    private String username = "";
    private String password = "";

    public CustomAuthenticationListener(Context context, String username, String password) {
        this.context = context;
        this.username = username;
        this.password = password;
    }

    @Override
    public void onAuthenticationChallengeReceived (
                        AuthenticationContext authContext,
                        JSONObject challenge, 
                        Context context) {

        // This is where developer would show a login screen, 
        // collect credentials and invoke
        // authContext.submitAuthenticationChallengeAnswer() API

        JSONObject challengeResponse = new JSONObject();
        try {
            challengeResponse.put("username", username);
            challengeResponse.put("password", password);
           
            authContext.submitAuthenticationChallengeAnswer(
                                            challengeResponse);
        } catch (JSONException e){

        // In case there was a failure in collecting credentials 
        // you need to report it back to the AuthenticationContext.
        // Otherwise Mobile Client Access client SDK will remain in a 
        // waiting-for-credentials state forever

            authContext.submitAuthenticationFailure(null);
        }
    }

扫描和监视应用程序的安全漏洞

IBM Bluemix 平台还提供了服务来帮助您扫描或监视应用程序。例如, IBM Application Security on Cloud 服务 可确保应用程序中没有安全漏洞。开发人员可在部署之前使用这些服务扫描 Web 和移动应用程序的漏洞,识别安全问题,并实现推荐的修复。此外,Mobile Client Access 服务提供了一个仪表板来监视和分析移动应用程序的使用情况以及设备中的安全事件,比如身份验证成功或失败。

结束语

对于分析来自 IoT 设备的敏感数据的应用程序,以及控制这些 IoT 设备上的功能的应用程序,安全性必须成为重要的关注领域。在本文中,我们重点介绍了如何保护在 IBM Bluemix 平台上运行的 IoT 应用层。您学习了如何使用 IBM Bluemix 安全性服务跨 Web 和移动应用程序来实现常见的用户身份验证和授权,以及如何将敏感数据安全地存储在 Cloudant 数据库中。

致谢

感谢 Bluemix 解决方案架构师 Subrata Saha 对本文提供了非常有意义的审阅和建议。

原文  http://www.ibm.com/developerworks/cn/iot/library/iot-trs-secure-iot-solutions3/index.html?ca=drs-
正文到此结束
Loading...