Создание приложения в реальном времени с использованием веб-сокета Laravel и Latchet

Я создаю закрытое приложение (пользователи должны пройти аутентификацию, чтобы использовать его). У меня возникли проблемы с идентификацией текущего аутентифицированного пользователя из моего сеанса Latchet. Поскольку apache не поддерживает долгоживущие соединения, я размещаю Latchet на отдельном экземпляре сервера. Это означает, что мои пользователи получают два идентификатора session_id. По одному на каждое соединение. Я хочу иметь возможность идентифицировать текущего пользователя для обоих подключений.

Мой клиентский код — это SPA на основе AngularJS. Для клиентской WS я использую реализацию Autobahn.ws WAMP v1. Платформа ab определяет методы аутентификации: http://autobahn.ws/js/reference_wampv1.html#session-authentication, но как именно мне это сделать?

Сохраняю ли я имя пользователя и пароль на клиенте и повторно передаю их после входа в систему (который, кстати, отделен от остальной части моего SPA)? Если да, то не будет ли это проблемой безопасности?

И что будет получать запрос на авторизацию на стороне сервера? Я не могу найти ни одного примера этого...

Пожалуйста помоги?

P.S. У меня недостаточно репутации, чтобы создать тег «Latchet», поэтому вместо этого я использую Ratchet (на котором Latchet построен).


person Øystein Amundsen    schedule 21.06.2014    source источник


Ответы (3)


Создайте службу angularjs с именем AuthenticationService, внедрите ее туда, где это необходимо, и вызовите ее с помощью:

AuthenticationService.check('login_name', 'password');

Этот код находится в файле с именем authentication.js. Предполагается, что автобан уже включен. Мне пришлось сильно отредактировать этот код, удалив все лишнее дерьмо, которое у меня было в нем, может быть синтаксическая ошибка или две, но идея есть.

angular.module(
  'top.authentication',
  ['top']
)

.factory('AuthenticationService', [ '$rootScope', function($rootScope) {
    return {
        check: function(aname, apwd) {
              console.log("here in the check function");
              $rootScope.loginInfo = { channel: aname, secret: apwd };
              var wsuri = 'wss://' + '192.168.1.11' + ':9000/';
              $rootScope.loginInfo.wsuri = wsuri;
              ab.connect(wsuri,
                  function(session) {
                      $rootScope.loginInfo.session = session;
                      console.log("connected to " + wsuri);
                      onConnect(session);
                  },
                  function(code,reason) {
                      $rootScope.loginInfo.session = null;
                      if ( code == ab.CONNECTION_UNSUPPORTED) {
                          console.log(reason);
                      } else {
                          console.log('failed');
                          $rootScope.isLoggedIn = 'false';
                      }
                  }
              );

              function onConnect(sess) {
                  console.log('onConnect');
                  var wi = $rootScope.loginInfo;
                  sess.authreq(wi.channel).then(
                    function(challenge) {
                        console.log("onConnect().then()");
                        var secret = ab.deriveKey(wi.secret,JSON.parse(challenge).authextra);
                        var signature = sess.authsign(challenge, secret);
                        sess.auth(signature).then(onAuth, ab.log);
                    },ab.log
                  );
              }

              function onAuth(permission) {
                  $rootScope.isLoggedIn = 'true';
                  console.log("authentication complete");
                  // do whatever you need when you are logged in..
              }
        }
    };
    }])

тогда вам нужен код (как вы указываете) на стороне сервера. Я предполагаю, что ваш веб-сокет на стороне сервера - это php-кодирование. Я не могу помочь с этим, не программировал на php больше года. В моем случае я использую python, я включаю механизм автобана, затем подкласс WampCraServerProtocol и заменяю несколько методов (onSessionOpen, getAuthPermissions, getAuthSecret, onAuthenticated и onClose). Как вы можете себе представить, это «другая сторона» угловой код стучится в дверь. Я не думаю, что автобан поддерживает php, поэтому вам придется самостоятельно программировать серверную часть аутентификации.

В любом случае, мой бэкенд работает гораздо больше, чем то, что описывает @oberstat. Я устанавливаю аутентификацию через старую школу https, создаю файл cookie сеанса, затем выполняю ajax, запрашивая «билет» (который является временным именем/паролем, который я связываю с сеансом аутентификации в Интернете). Это одноразовое имя/пароль, и его нужно использовать в течение нескольких секунд, иначе он исчезнет. Дело в том, что мне не нужно хранить учетные данные пользователя, у меня уже есть файл cookie/сеанс, который я могу создавать билеты, которые можно использовать. это также имеет приятный побочный эффект: мой сеанс ajax становится связанным с моим сеансом веб-сокета, запрос на любой из них относится к тому же сеансу в бэкэнде.

-g

person Greg    schedule 21.06.2014
comment
Ваше последнее предложение об аккуратном побочном эффекте сделало все кристально ясным. Выполняя аутентификацию, вы также делаете существующий сеанс доступным для WS. Большое спасибо. Я попробую это как можно скорее. - person Øystein Amundsen; 22.06.2014
comment
groups.google.com/forum/#!topic/wampws/P166tcIUaFI, этот поток указал мне на эту реализацию автобана php! Вот URL: github.com/voryx/Thruway - person Greg; 23.06.2014

Я могу дать вам пару советов относительно WAMP-CRA, который представляет собой механизм аутентификации, о котором идет речь:

WAMP-CRA не отправляет пароли по сети. Работает по схеме вызов-ответ. Клиент и сервер имеют общий секрет. Чтобы аутентифицировать клиента, сервер отправит вызов (что-то случайное), который клиент должен подписать, используя секрет. И только подпись отправляется обратно. Клиент может хранить секрет в локальном хранилище браузера. Он никогда не отправляется.

В варианте выше, подпись запроса, отправляемого сервером, не подписывается напрямую внутри клиента, но клиент может разрешить создание подписи из запроса Ajax. Это полезно, когда клиент уже аутентифицирован с использованием других средств (например, на основе классического файла cookie), а затем подпись может быть выполнена в классическом веб-приложении, которое выполняло аутентификацию.

person oberstet    schedule 21.06.2014
comment
Я не совсем уверен, как это должно работать. Имя пользователя/пароль ЯВЛЯЕТСЯ аутентификацией типа запрос/ответ. И между сервером ws и клиентом единственная сторона, осведомленная о какой-либо личности, — это клиент. Клиент хранит файл cookie сеанса, который я мог бы использовать в качестве секрета, но я не могу запросить у Laravel сеанс с определенным идентификатором, не так ли? - person Øystein Amundsen; 21.06.2014

Хорошо, Грег был достаточно любезен, чтобы предоставить полный пример реализации клиента, так что я больше ничего не буду делать по этому поводу. Он работает с несколькими настройками и модификациями практически для любого варианта использования, который я могу придумать. Я отмечу его ответ как правильный. Но его вклад касался только теории реализации серверной части, поэтому я попытаюсь заполнить пробелы здесь для постчетности.

Однако я должен указать, что решение здесь не является полным, поскольку оно не дает мне общего сеанса между моим соединением SPA/REST и моим соединением WS.

Я обнаружил, что запрос аутентификации, передаваемый автобаном, на самом деле является вариантом RPC и по какой-то причине имеет жестко закодированные названия тем, любопытно напоминающие обычные URL-адреса:

- 'http://api.wamp.ws/procedure#authreq' - for auth requests
- 'http://api.wamp.ws/procedure#auth'    - for signed auth client responses

Мне нужно было создать еще два маршрута в моем Laravel route.php

// WS CRA routes
Latchet::topic('http://api.wamp.ws/procedure#authreq', 'app\\socket\\AuthReqController');
Latchet::topic('http://api.wamp.ws/procedure#auth',    'app\\socket\\AuthReqController');

Теперь у контроллера Latchet есть 4 метода: subscribe, publish, call и unsubscribe. Поскольку и вызовы authreq, и вызовы auth, сделанные автобаном, являются вызовами RPC, они обрабатываются методом call на контроллере.

Решение, впервые предложенное oberstet, а затем поддержанное Грегом, описывает временный ключ аутентификации и секрет, генерируемые по запросу и удерживаемые временно ровно столько времени, сколько требуется для проверки процедурой WS CRA. Поэтому я создал конечную точку REST, которая генерирует постоянную пару значений ключа. Конечная точка здесь не указана, так как я уверен, что это тривиально.

class AuthReqController extends BaseTopic {
    public function subscribe ($connection, $topic) {    }

    public function publish ($connection, $topic, $message, array $exclude, array $eligible) {    }

    public function unsubscribe ($connection, $topic) {    }

    public function call ($connection, $id, $topic, array $params) {
        switch ($topic) {
            case 'http://api.wamp.ws/procedure#authreq':
                return $this->getAuthenticationRequest($connection, $id, $topic, $params);
            case 'http://api.wamp.ws/procedure#auth':
                return $this->processAuthSignature($connection, $id, $topic, $params);
        }
    }

    /**
     * Process the authentication request
     */
    private function getAuthenticationRequest ($connection, $id, $topic, $params) {
        $auth_key = $params[0]; // A generated temporary auth key
        $tmpUser  = $this->getTempUser($auth_key); // Get the key value pair as persisted from the temporary store.
        if ($tmpUser) {
            $info = [
                'authkey'   => $tmpUser->username,
                'secret'    => $tmpUser->secret,
                'timestamp' => time()
            ];
            $connection->callResult($id, $info); 
        } else {
            $connection->callError($id, $topic, array('User not found'));
        }
        return true;
    }

    /**
     * Process the final step in the authentication
     */
    private function processAuthSignature ($connection, $id, $topic, $params) {
        // This should do something smart to validate this response.

        // The session should be ours right now. So store the Auth::user()
        $connection->user = Auth::user(); // A null object is stored.
        $connection->callResult($id, array('msg' => 'connected'));
    }

    private function getTempUser($auth_key) {
        return TempAuth::findOrFail($auth_key);
    }
}

Теперь где-то здесь я ошибся. Потому что, если бы я должен был унаследовать сеанс ajax, который держит мое приложение, я мог бы вызывать Auth::user() из любого из моих других контроллеров на основе WS Latchet и автоматически отображать текущего пользователя, вошедшего в систему. Но это не так. Так что, если кто-то увидит, что я делаю неправильно, дайте мне крик. Пожалуйста!

Поскольку я не могу получить общий сеанс, я в настоящее время обманываю, передавая настоящее имя пользователя как вызов RPC вместо выполнения полного CRA.

person Øystein Amundsen    schedule 24.06.2014