שילוב LTI נכשל לעיתים קרובות עקב אי-התאמות גרסאות, אובדן סשן או תצורת JWKS שגויה. המהנדסים שלנו פותרים בעיות אלה מקצה לקצה—מביקורת על ה-LMS הנוכחי שלך ועד חיבור כל כלי תואם LTI. צברנו ספרייה של שגיאות טיפוסיות ותבניות מוכנות, מה שמקצר משמעותית את זמן הפיתוח.
לפי מפרט IMS Global LTI 1.3, שימוש ב-OAuth 2.0 ו-JWKS מספק רמת אבטחה גבוהה יותר בהשוואה ל-LTI 1.1.
למה LTI 1.3 עדיף על LTI 1.1?
LTI 1.1 משתמש ב-OAuth 1.0—פרוטוקול מיושן עם חתימות שבריריות. LTI 1.3 מבוסס על OAuth 2.0 ו-OpenID Connect: מאובטח יותר, שקוף וקל יותר לניפוי שגיאות. אסימוני JWT עם מפתחות JWKS מונעים זיוף בקשות. ספקים רבים (Coursera, Zoom, Adobe) עוברים ל-LTI 1.3—בלעדיו, לא תוכל לחבר כלים מודרניים.
| תכונה | LTI 1.1 | LTI 1.3 |
|---|---|---|
| פרוטוקול | OAuth 1.0 | OAuth 2.0 + OIDC |
| חתימה | HMAC-SHA1 | RSA/JWT |
| ניהול מפתחות | סוד משותף (סטטי) | JWKS (רוטציה) |
| אבטחה | נמוכה | גבוהה |
| זמן אינטגרציה | 2-3 ימים | 3-5 ימים |
כיצד אנו מגדירים LTI 1.3 ב-LMS שלך
ה-LMS משמש כפלטפורמה (לפי IMS). אנו מאתחלים את התהליך באמצעות OIDC Login Request ומייצרים JWT. להלן קוד טיפוסי עם ספריית ltijs:
import { Provider } from 'ltijs'; // npm install ltijs
// Настройка LMS как LTI Platform
Provider.setup(
process.env.LTI_ENCRYPTION_KEY!, // 32+ символов для шифрования cookies
{
url: process.env.DATABASE_URL!,
plugin: require('ltijs-postgresql'), // адаптер для PostgreSQL
},
{
cookies: {
secure: true,
sameSite: 'None',
},
devMode: process.env.NODE_ENV !== 'production',
}
);
// Регистрация внешнего инструмента
await Provider.registerPlatform({
url: 'https://tool.example.com',
name: 'Kahoot Integration',
clientId: process.env.KAHOOT_CLIENT_ID!,
authenticationEndpoint: 'https://tool.example.com/lti/auth',
accesstokenEndpoint: 'https://tool.example.com/lti/token',
authConfig: {
method: 'JWK_SET',
key: 'https://tool.example.com/.well-known/jwks.json',
},
});
// Обработчик запуска инструмента
Provider.onConnect(async (token, req, res) => {
const { email, name, role } = token.userInfo;
const contextId = token.platformContext.context?.id;
// Проверить/создать пользователя во внешнем инструменте
res.json({ token: token.jwt });
});
await Provider.deploy({ serverless: true });
LTI 1.1—לכלים מדור קודם
חלק מהספקים (Phet, מבחנים מסוימים) עדיין משתמשים ב-LTI 1.1 עם חתימת OAuth 1.0:
import oauth from 'oauth-signature';
function launchLti11(
launchUrl: string,
consumerKey: string,
consumerSecret: string,
params: Record<string, string>
): { url: string; method: 'POST'; body: string } {
const baseParams: Record<string, string> = {
lti_message_type: 'basic-lti-launch-request',
lti_version: 'LTI-1p0',
oauth_callback: 'about:blank',
oauth_consumer_key: consumerKey,
oauth_nonce: crypto.randomUUID().replace(/-/g, ''),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: String(Math.floor(Date.now() / 1000)),
oauth_version: '1.0',
...params,
};
const signature = oauth.generate('POST', launchUrl, baseParams, consumerSecret, '');
baseParams.oauth_signature = signature;
const body = new URLSearchParams(baseParams).toString();
return { url: launchUrl, method: 'POST', body };
}
// Страница запуска LTI инструмента
app.get('/courses/:courseId/tools/:toolId/launch', authenticate, async (req, res) => {
const tool = await db.ltiTools.findById(req.params.toolId);
const enrollment = await db.enrollments.findByCourseAndUser(
req.params.courseId,
req.user.id
);
if (tool.version === '1.1') {
const launch = launchLti11(tool.launch_url, tool.consumer_key, tool.consumer_secret, {
resource_link_id: `${req.params.courseId}-${req.params.toolId}`,
resource_link_title: tool.name,
user_id: req.user.id,
lis_person_name_full: req.user.name,
lis_person_contact_email_primary: req.user.email,
roles: enrollment.role === 'instructor' ? 'Instructor' : 'Student',
context_id: req.params.courseId,
context_title: enrollment.courseTitle,
});
// Авто-сабмит форму через HTML
res.send(`
<!DOCTYPE html>
<html>
<body>
<form id="lti" method="POST" action="${launch.url}">
${Object.entries(Object.fromEntries(new URLSearchParams(launch.body)))
.map(([k, v]) => `<input type="hidden" name="${k}" value="${v}" />`)
.join('\n')}
</form>
<script>document.getElementById('lti').submit();</script>
</body>
</html>
`);
}
});
שמירת תצורת כלי
CREATE TABLE lti_tools (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID REFERENCES courses(id),
name VARCHAR(255) NOT NULL,
version VARCHAR(10) NOT NULL, -- '1.1' | '1.3'
-- LTI 1.1
launch_url TEXT,
consumer_key VARCHAR(255),
consumer_secret TEXT,
-- LTI 1.3
client_id VARCHAR(255),
platform_id VARCHAR(255),
deployment_id VARCHAR(255),
oidc_auth_url TEXT,
jwks_url TEXT,
-- Настройки
open_in_new_tab BOOLEAN DEFAULT false,
custom_params JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
); קבלת תוצאות דרך LTI Outcomes (1.1)
// Инструмент может отправить оценку обратно в LMS
app.post('/lti/grade', async (req, res) => {
const { sourcedId, score, action } = parseLtiOutcomesXml(req.body);
// sourcedId содержит userId и resourceLinkId
const [userId, resourceId] = parseLisResultSourcedId(sourcedId);
await db.ltiGrades.upsert({
userId,
resourceId,
score: Number(score),
receivedAt: new Date(),
});
// Обновить прогресс курса
await updateCourseProgress(userId, resourceId, Number(score));
res.type('application/xml').send(`
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXResponseHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>${crypto.randomUUID()}</imsx_messageIdentifier>
<imsx_statusInfo>
<imsx_codeMajor>success</imsx_codeMajor>
<imsx_severity>status</imsx_severity>
</imsx_statusInfo>
</imsx_POXResponseHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody><replaceResultResponse /></imsx_POXBody>
</imsx_POXEnvelopeResponse>
`);
}); מה כלול בעבודת אינטגרציית LTI?
- ביקורת על ארכיטקטורת ה-LMS הנוכחית: זיהוי צווארי בקבוק (שאילתות N+1, אובדן סשן), הערכת תאימות לכלים הנדרשים.
- עיצוב סכמת אחסון לתצורות כלים (טבלת lti_tools עם תמיכה בשתי גרסאות LTI).
- הטמעת תהליך השקה של LTI 1.1 ו/או LTI 1.3 על המחסנית שלך (Node.js, Python, PHP, Ruby—כל אחת).
- אינטגרציה עם כלי חיצוני: רישום, השקת בדיקה, החזרת ציונים (Outcomes).
- כתיבת תיעוד למנהלי מערכת ולמרצים על הוספת כלים חדשים.
- הכשרת הצוות שלך: כיצד לנפות שגיאות, לעדכן מפתחות, לעבוד עם JWKS.
- תמיכה של 30 יום לאחר השחרור: תיקוני באגים, ייעוץ.
שגיאות נפוצות באינטגרציית LTI
- אי-התאמת סוג משתמש: בלבול בין תפקידי Instructor ו-Student ב-JWT.
- שגיאות JWKS: מפתחות פגים או לא מעודכנים.
- בעיות CORS: כלים חיצוניים חוסמים בקשות.
- תצורת OIDC Login Request שגויה: URI הפנייה שגוי.
לוחות זמנים ועלות
אינטגרציית LTI 1.1 (Consumer + Launch + Grades) — 3–5 ימים. LTI 1.3 עם תהליך OIDC — שבוע. תמיכה בשתי הגרסאות בתוספת ממשק ניהול כלים — שבועיים. העלות נקבעת באופן אישי, ושימוש בתבניות מוכנות יכול להפחית משמעותית את התקציב.
הניסיון והאחריות שלנו
מקרה בוחן: שילוב Kahoot ב-LMS אוניברסיטאי. נדרשה תמיכה ב-LTI 1.3, מכיוון ש-Kahoot עבר לסטנדרט החדש. הטמענו את תהליך OIDC, הגדרנו JWKS ובדקנו החזרת ציונים. כתוצאה מכך, הסטודנטים קיבלו ממשק אחיד ללא אימות חוזר. קבלו ייעוץ על אינטגרציית LTI עוד היום. צרו קשר – נעריך את המשימה שלכם ונציע פתרון תוך 2 ימי עסקים.







