开发LTI工具的基础步骤是对传入的启动信息进行验证。相当于常规网站对登陆用户提供的用户名及密码进行身份验证。如果LTI工具本身具有验证用户身份的代码模块,则这部分代码在开发LTI应用时可以复用。
确认LTI启动请求包含以下属性:
1. 是 HTTP POST 请求
2. POST参数(必填项)
lti_message_type=basic-lti-launch-request
lti_version=LTI-1p0
oauth_consumer_key非空
resource_link_id非空
虽然LTI启动请求的必须参数很少,但是只要有缺失,就会被认定非法LTI请求,系统应拒绝连接。
// Check it is a POST request,OK初始值设置为true $ok = $ok && $_SERVER['REQUEST_METHOD'] === 'POST'; // Check the LTI message type $ok = $ok && isset($_POST['lti_message_type']) && ($_POST['lti_message_type'] === 'basic-lti-launch-request'); // Check the LTI version $ok = $ok && isset($_POST['lti_version']) && ($_POST['lti_version'] === 'LTI-1p0'); // Check a consumer key exists $ok = $ok && !empty($_POST['oauth_consumer_key']); // Check a resource link ID exists $ok = $ok && !empty($_POST['resource_link_id']);
LTI启动请求使用OAuth 1进行签名,确保LTI工具收到的数据与LMS发送的数据相同。同时,确保该消息不是已经接收和处理的消息副本。
OAuth签名过程,将下列参数插入到启动请求传递的数据中:
oauth_callback:about:blank
oauth_consumer_key:LTI工具颁发给LMS的consumer_key,用于匹配secret
oauth_nonce:
oauth_signature:
oauth_signature_method:HMAC-SHA1
oauth_timestamp:
oauth_version:1.0
检查签名消息所涉及的大部分工作都由OAuth库处理,可用于大多数编程语言。Oauth库根据consumer_key来匹配secret,并提供了确认nonce值不重复的方法。它执行的任务是:
// Check the consumer key is recognised,For example, a tool consumer with a key of imsglobal.org and a secret of ThisIsABigSecret! $ok = $ok && array_key_exists($_POST['oauth_consumer_key'], $tool_consumer_secrets); // Check the OAuth credentials (nonce, timestamp and signature) if ($ok) { require_once('lib.php'); // load class to act as an OAuth data store try { $store = new ImsOAuthDataStore($_POST['oauth_consumer_key'], $CONSUMERS_TABLE[$_POST['oauth_consumer_key']]); $server = new OAuthServer($store); $method = new OAuthSignatureMethod_HMAC_SHA1(); $server->add_signature_method($method); $request = OAuthRequest::from_request(); $server->verify_request($request); } catch (Exception $e) { $ok = FALSE; } }
class ImsOAuthDataStore extends OAuthDataStore { private $consumer_key = NULL; private $consumer_secret = NULL; public function __construct($consumer_key, $consumer_secret) { $this->consumer_key = $consumer_key; $this->consumer_secret = $consumer_secret; } function lookup_consumer($consumer_key) { return new OAuthConsumer($this->consumer_key, $this->consumer_secret); } function lookup_token($consumer, $token_type, $token) { return new OAuthToken($consumer, ''); } function lookup_nonce($consumer, $token, $nonce, $timestamp) { return FALSE; // If a persistent store is available nonce values should be retained for a period and checked here }
function new_request_token($consumer, $callback = null) { return NULL; } function new_access_token($token, $consumer, $verifier = null) { return NULL; } }
Sample PHP Code
此示例代码假定LTI工具需要知道用户是谁(User_id)以及角色是教师还是学生(role)。此外,唯一被认可的角色是Instructor和Learner ;
// Check for a user ID $ok = $ok && !empty($_POST['user_id']); // Check for a role $ok = $ok && !empty($_POST['roles']); if ($ok) { // Check that the user is either a Learner or an Instructor $roles = parseRoles($_POST['roles']); $ok = in_array('urn:lti:role:ims/lis/Learner', $roles) || in_array('urn:lti:role:ims/lis/Instructor', $roles);}
此过程可能与其他处理登录页面的代码模块具有相似性。但对于LTI启动请求,除登录过程外,还需要下列操作; 例如:
建立的用户会话通常将被赋予一个ID,该ID通过cookie或查询参数与来自用户浏览器的请求一起传递; 主要目标是允许LTI工具在从用户的浏览器接收到HTTP请求时知道用户是谁,而无需再次验证它们并验证他们授权访问所请求的资源。
// Cancel any existing session session_start(); $_SESSION = array(); session_destroy(); session_start(); // Initialise the user session $_SESSION['userId'] = $_POST['user_id']; $_SESSION['isInstructor'] = in_array('urn:lti:role:ims/lis/Instructor', $roles); $_SESSION['firstname'] = $_POST['lis_person_name_given']; $_SESSION['lastname'] = $_POST['lis_person_name_family']; $_SESSION['consumerKey'] = $_POST['oauth_consumer_key']; $_SESSION['resourceLinkId'] = $_POST['resource_link_id']; if (!$_SESSION['isInstructor'] && isset($_POST['lis_outcome_service_url']) && isset($_POST['lis_result_sourcedid'])) { $_SESSION['outcomesUrl'] = $_POST['lis_outcome_service_url']; $_SESSION['resultSourcedId'] = $_POST['lis_result_sourcedid']; }