ADN Open CIS
Сообщество программистов Autodesk в СНГ

14/04/2017

Защита токена Forge Viewer при помощи прокси-сервера

Эта статья иллюстрирует спосооб укрепления безопасность вашего приложения Forge Viewing Application при помощи прокси-сервера, чтобы избежать передачи токена в клиентский код JavaScript.

Суть вопроса 

Чтобы Viewer мог получить доступ к просматриваемым ресурсам в Autodesk Cloud и загрузить модели на клиентской странице, ему необходимо добавить токен на предъявителя (bearer) к заголовкам запросов.

Чтобы сделать это возможным, основной подход заключается в том, чтобы вывести маршрут на ваш сервер, который возвращает токен. В таком случае тогда ваш клиентский код будет использовать этот маршрут для запроса токена всякий раз, когда ему это понадобится. Эти шаги описаны в руководстве basic viewer .

Минимальная область доступа, требуемая на данный момент, - «data:read», однако эта область также позволяет выполнять GET-запросы для buckets и объектов OSS. Было бы относительно тривиально, если бы злоумышленник проанализиров исходный код вашей страницы или отслеживая http-трафик, перехватил маркер и URN модели, а с помощью 64-битного декодирования смог бы собрать ключевые файлы, хранящиеся в этом bucket, т.е. загрузить оригинальные файлы САПР.

Обходные пути

Существует два эффективных способа предотвратить это:

1. Решение для платформы Forge:

На данный момент ведутся внутренние обсуждения, для обеспечения защиты данного рабочего процесса. Одна из возможностей заключается в том, чтобы предоставить область доступа «token:read», которая позволяет наблюдателю загружать только просматриваемые ресурсы, но не позволяет получить доступ к ключевым файлам в OSS. Также есть другие возможности, которые рассматривает команда разработчиков, но пока еще рано делиться этими темами публично.

2. Стороннее решение: 

Использовать прокси в приложении сервера, которое безопасно добавит токен к запросам просмотрщика, и тем самым полностью защитит его от воздействия на клиента. Это решение, которое сторонние приложения могут реализовать сразу же, не дожидаясь реализации на платформе Forge, поэтому я расскажу об этом сегодня в этой статье.

Влияние на ваш существующий код сервера/клиента должно быть минимальным. Достигнуть этого можно следующим образом:

На стороне сервера

Сегодня я предоставлю код Node.js для достижения этого, позже мы покажем вам, как реализовать это в приложении ASP.Net, но принцип остается тем же.

Добавьте дополнительный маршрут в свое приложение, которое будет обрабатывать все запросы просмотрщика типа GET:

Код - JavaScript: [Выделить]
  1. import LMVProxy from './api/endpoints/lmv-proxy'
  2. var app = express()
  3. app.get('/lmv-proxy/*', LMVProxy.get)
  4. // ... инициализировать другие маршруты ...

Код для прокси-маршрута показан ниже:

Код - JavaScript: [Выделить]
  1. /////////////////////////////////////////////////////////////////
  2. // Прокси-сервер Forge Viewer
  3. // Philippe Leefsma, февраль 2017 г.
  4. //
  5. /////////////////////////////////////////////////////////////////
  6.                 import ServiceManager from '../services/SvcManager'
  7.                 import https from 'https'
  8.                 import path from 'path'
  9. /////////////////////////////////////////////////////////////////
  10. //
  11. //
  12. /////////////////////////////////////////////////////////////////
  13.                 const EXTENSIONS = {
  14.                   gzip: [ '.json.gz', '.bin', '.pack' ],
  15.                   json: [ '.json.gz', '.json' ]
  16. }
  17.                 const WHITE_LIST = [
  18.                   'if-modified-since',
  19.                   'if-none-match',
  20.                   'accept-encoding',
  21.                   'x-ads-acm-namespace',      // Forge Data Management API
  22.                   'x-ads-acm-check-groups'    // Forge Data Management API
  23. ]
  24. /////////////////////////////////////////////////////////////////
  25. //
  26. //
  27. /////////////////////////////////////////////////////////////////
  28.                 function fixContentHeaders (req, res) {
  29.                   // DS не возвращает заголовок кодирования содержимого
  30.                   // для gzip и других файлов, которые мы знаем, gzipped,
  31.                   // поэтому мы добавляем его здесь. // Просмотрщику необходимы
  32.                   // файлы gzip, несжатые браузером
  33.                   if ( EXTENSIONS.gzip.indexOf (path.extname (req.path)) > -1 ) {
  34.                     res.set ('content-encoding', 'gzip')
  35.   }
  36.                   if ( EXTENSIONS.json.indexOf (path.extname (req.path)) > -1 ){
  37.                     res.set ('content-type', 'application/json')
  38.   }
  39. }
  40. /////////////////////////////////////////////////////////////////
  41. //
  42. //
  43. /////////////////////////////////////////////////////////////////
  44.                 function setCORSHeaders (res) {
  45.                     res.set('access-control-allow-origin', '*')
  46.                     res.set('access-control-allow-credentials', false)
  47.                     res.set('access-control-allow-headers',
  48.                       "Origin, X-Requested-With, Content-Type, Accept")
  49. }
  50. /////////////////////////////////////////////////////////////////
  51. //
  52. //
  53. /////////////////////////////////////////////////////////////////
  54.                 function proxyClientHeaders (clientHeaders, upstreamHeaders) {
  55.                   WHITE_LIST.forEach(h => {
  56.                       const hval = clientHeaders[h]
  57.                       if (hval) {
  58.                           upstreamHeaders[h] = hval
  59.       }
  60.   })
  61.                   // исправить проблему OSS, не принимающую
  62.                   // etag, заключенный в двойные кавычки ...
  63.                   const etag = upstreamHeaders['if-none-match']
  64.                   if (etag) {
  65.                     if(etag[0] === '"' && etag[etag.length - 1] === '"') {
  66.                         upstreamHeaders['if-none-match'] =
  67.                           etag.substring(1, etag.length - 1);
  68.     }
  69.   }
  70. }
  71. /////////////////////////////////////////////////////////////////
  72. //
  73. //
  74. /////////////////////////////////////////////////////////////////
  75.                 function Proxy(endpoint, authHeaders) {
  76.                   this.authHeaders = authHeaders
  77.                   this.endpoint = endpoint
  78. }
  79. /////////////////////////////////////////////////////////////////
  80. //
  81. //
  82. /////////////////////////////////////////////////////////////////
  83.                 Proxy.prototype.request = function(req, res, url) {
  84.                     const options = {
  85.                       host:       this.endpoint,
  86.                       port:       443,
  87.                       path:       url,
  88.                       method:     'GET', // только прокси GET
  89.                       headers:    this.authHeaders
  90.     }
  91.                     proxyClientHeaders(req.headers, options.headers)
  92.                     const creq = https.request(options, (cres) => {
  93.                         // установить кодировку
  94.                         //cres.setEncoding('utf8');
  95.                         for (let h in cres.headers) {
  96.                             res.set(h, cres.headers[h])
  97.         }
  98.                         fixContentHeaders(req, res)
  99.                         setCORSHeaders(res)
  100.                         res.writeHead(cres.statusCode)
  101.                         cres.pipe(res)
  102.                         cres.on('error', (e) => {
  103.                           // мы получили ошибку,
  104.                           // вернуть ошибку 500 клиенту и занести её в лог
  105.                           debug.error(e.message)
  106.                           res.end()
  107.         })
  108.     })
  109.                     creq.end()
  110. }
  111. /////////////////////////////////////////////////////////////////
  112. //
  113. //
  114. /////////////////////////////////////////////////////////////////
  115.                 function proxyGet (req, res) {
  116.                   const forgeSvc = ServiceManager.getService(
  117.                     'ForgeSvc')
  118.                   forgeSvc.get2LeggedToken().then((token) => {
  119.                     const url = req.url.replace (/^\/lmv\-proxy/gm, '')
  120.                     const endpoint = 'developer.api.autodesk.com'
  121.                     const authHeaders = {
  122.                       Authorization: `Bearer ${token.access_token}`
  123.     }
  124.                     const proxy = new Proxy(endpoint, authHeaders)
  125.                     proxy.request(req, res, url)
  126.   })
  127. }
  128. /////////////////////////////////////////////////////////////////
  129. //
  130. //
  131. /////////////////////////////////////////////////////////////////
  132.                 exports.get = proxyGet
  133. view rawlmv-proxy.js hosted with ❤ by GitHub

Клиент

Новая версия API была добавлена в просмотрщик версии 2.13 и будет слегка изменена в версии 2.14. В принципе вы можете удалить поле getAccessToken параметрах инициализации и указать прокси-маршрут, который просмотрщик должен использовать для своих GET-запросов:

Код - JavaScript: [Выделить]
  1. const options = {
  2.   env: 'AutodeskProduction'
  3.   // getAccessToken: ... // не требуется, токен, предоставляемый прокси на стороне сервера
  4. )
  5. Autodesk.Viewing.Initializer (options, () => {
  6.   
  7.   //2.13
  8.   Autodesk.Viewing.setApiEndpoint(
  9.     window.location.origin + '/lmv-proxy')
  10.   //2.14 - пока не используется в PROD
  11.   //Autodesk.Viewing.setEndpointAndApi(
  12.   //  window.location.origin + '/lmv-proxy', 'modelDerivativeV2')
  13.   // Инстанцируем просмотрщик и загружаем модель, как и раньше ...
  14. })

Et voilà, теперь вы можете наслаждаться безопасным просмотром! Полные примеры использования этого подхода доступны в Node.JS модулях forge-react-boiler.nodejs и forge-rcdb.nodejs

Источник: https://forge.autodesk.com/blog/securing-your-forge-viewer-token-behind-proxy

Philippe Leefsma

Автор перевода: Дмитрий Емельянов

Обсуждение: http://adn-cis.org/forum/index.php?topic=

Опубликовано 14.04.2017