Защита токена 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:
- import LMVProxy from './api/endpoints/lmv-proxy'
- var app = express()
- app.get('/lmv-proxy/*', LMVProxy.get)
- // ... инициализировать другие маршруты ...
Код для прокси-маршрута показан ниже:
- /////////////////////////////////////////////////////////////////
- // Прокси-сервер Forge Viewer
- // Philippe Leefsma, февраль 2017 г.
- //
- /////////////////////////////////////////////////////////////////
- import ServiceManager from '../services/SvcManager'
- import https from 'https'
- import path from 'path'
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- const EXTENSIONS = {
- gzip: [ '.json.gz', '.bin', '.pack' ],
- json: [ '.json.gz', '.json' ]
- }
- const WHITE_LIST = [
- 'if-modified-since',
- 'if-none-match',
- 'accept-encoding',
- 'x-ads-acm-namespace', // Forge Data Management API
- 'x-ads-acm-check-groups' // Forge Data Management API
- ]
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- function fixContentHeaders (req, res) {
- // DS не возвращает заголовок кодирования содержимого
- // для gzip и других файлов, которые мы знаем, gzipped,
- // поэтому мы добавляем его здесь. // Просмотрщику необходимы
- // файлы gzip, несжатые браузером
- if ( EXTENSIONS.gzip.indexOf (path.extname (req.path)) > -1 ) {
- res.set ('content-encoding', 'gzip')
- }
- if ( EXTENSIONS.json.indexOf (path.extname (req.path)) > -1 ){
- res.set ('content-type', 'application/json')
- }
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- function setCORSHeaders (res) {
- res.set('access-control-allow-origin', '*')
- res.set('access-control-allow-credentials', false)
- res.set('access-control-allow-headers',
- "Origin, X-Requested-With, Content-Type, Accept")
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- function proxyClientHeaders (clientHeaders, upstreamHeaders) {
- WHITE_LIST.forEach(h => {
- const hval = clientHeaders[h]
- if (hval) {
- upstreamHeaders[h] = hval
- }
- })
- // исправить проблему OSS, не принимающую
- // etag, заключенный в двойные кавычки ...
- const etag = upstreamHeaders['if-none-match']
- if (etag) {
- if(etag[0] === '"' && etag[etag.length - 1] === '"') {
- upstreamHeaders['if-none-match'] =
- etag.substring(1, etag.length - 1);
- }
- }
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- function Proxy(endpoint, authHeaders) {
- this.authHeaders = authHeaders
- this.endpoint = endpoint
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- Proxy.prototype.request = function(req, res, url) {
- const options = {
- host: this.endpoint,
- port: 443,
- path: url,
- method: 'GET', // только прокси GET
- headers: this.authHeaders
- }
- proxyClientHeaders(req.headers, options.headers)
- const creq = https.request(options, (cres) => {
- // установить кодировку
- //cres.setEncoding('utf8');
- for (let h in cres.headers) {
- res.set(h, cres.headers[h])
- }
- fixContentHeaders(req, res)
- setCORSHeaders(res)
- res.writeHead(cres.statusCode)
- cres.pipe(res)
- cres.on('error', (e) => {
- // мы получили ошибку,
- // вернуть ошибку 500 клиенту и занести её в лог
- debug.error(e.message)
- res.end()
- })
- })
- creq.end()
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- function proxyGet (req, res) {
- const forgeSvc = ServiceManager.getService(
- 'ForgeSvc')
- forgeSvc.get2LeggedToken().then((token) => {
- const url = req.url.replace (/^\/lmv\-proxy/gm, '')
- const endpoint = 'developer.api.autodesk.com'
- const authHeaders = {
- Authorization: `Bearer ${token.access_token}`
- }
- const proxy = new Proxy(endpoint, authHeaders)
- proxy.request(req, res, url)
- })
- }
- /////////////////////////////////////////////////////////////////
- //
- //
- /////////////////////////////////////////////////////////////////
- exports.get = proxyGet
- view rawlmv-proxy.js hosted with ❤ by GitHub
Клиент
Новая версия API была добавлена в просмотрщик версии 2.13 и будет слегка изменена в версии 2.14. В принципе вы можете удалить поле getAccessToken параметрах инициализации и указать прокси-маршрут, который просмотрщик должен использовать для своих GET-запросов:
- const options = {
- env: 'AutodeskProduction'
- // getAccessToken: ... // не требуется, токен, предоставляемый прокси на стороне сервера
- )
- Autodesk.Viewing.Initializer (options, () => {
- //2.13
- Autodesk.Viewing.setApiEndpoint(
- window.location.origin + '/lmv-proxy')
- //2.14 - пока не используется в PROD
- //Autodesk.Viewing.setEndpointAndApi(
- // window.location.origin + '/lmv-proxy', 'modelDerivativeV2')
- // Инстанцируем просмотрщик и загружаем модель, как и раньше ...
- })
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