【问题标题】:Authenticating Parse.com User and Session Server-side验证 Parse.com 用户和会话服务器端
【发布时间】:2015-07-18 21:02:43
【问题描述】:

我目前正在探索 Parse 平台,方法是使用 Parse Javascript SDK 构建一些示例客户端应用程序。我决定将大部分与 Parse 相关的逻辑从应用程序中抽象出来,并抽象为在 VPS 上运行的 RESTful API。目的是使应用程序尽可能与后端无关。

我构建的 RESTful API 只是一个使用 Node.js Parse 包的 Express.js 应用程序。

我的问题是:当我想向我的 REST API 发出请求时,验证发出请求的用户的最佳方法是什么?

示例:考虑一个用户可以交朋友的应用:

  1. 用户使用 Parse.User.logIn() 方法在客户端登录。
  2. 客户端点击我的 REST API 上的端点 /users/me/friends,它会检查用户是否有有效的会话并返回用户的朋友列表。

所以这需要服务器端 Parse 代码来确定:

  1. 提出请求的用户是谁,以及
  2. 如果当前用户有一个有效的会话

我考虑过使用以下方法:

  1. 当用户登录客户端时,她/他会收到一个会话令牌。
  2. 在发出 API 请求时将此会话令牌作为标头附加。
  3. 在服务器上,使用此会话令牌结合 Parse.User.become() 方法来设置当前用户并执行所需的逻辑:
  4. 所以 express 应用会处理上面的例子:
router.get("/users/me/friends", function(req, res) {
    var sessionToken = req.headers["sessionToken"];
    Parse.User.become(sessionToken).then(function(user) {
        /* Valid user and session: find and return the users friends */
    }, function(error) {
        /* Invalid: return an error status and the error */
    });
});

这是处理这种情况的有效方法吗?如果我继续这种方式,我应该注意什么问题?谢谢!

【问题讨论】:

  • 为什么不直接使用默认的 Parse REST API 或 Cloud Code?​​i> (1) 我可能希望应用程序与解析之外的其他几个服务集成。 (2) 我可能想要几个遵循相同业务规则的界面(即移动应用程序和 Web 应用程序)。使用通用 API 可以避免代码重复并将复杂的模型逻辑从客户端应用程序中抽象出来。

标签: javascript rest parse-platform


【解决方案1】:

我一直在问自己同样的问题,我提出的解决方案与您所解释的非常相似。我正在开发一个 Android 应用程序,它只使用 Parse 作为它的用户和会话部分,其他一切都将是 MySQL、Redis 或我计划在未来包装的任何其他数据源。我已经使用 PHP 和 Slim 框架在 VPS 上开发了我自己的 Rest API。我通过在我自己生成的用户表中添加一个 ApiKey 列来利用 Parse 中的身份验证部分,然后还利用已经为每个用户创建和管理的 SessionToken。当用户对我的 Web 服务进行经过身份验证的调用时,他们会在每个请求标头中提供 api 密钥和会话令牌,并且当我的服务器收到请求时,它会通过 Parse 结束验证来确保 apiKey + sessionToken 对有效。每次用户重新登录时都会重新创建 sessionToken,因此这在一定程度上保证了它的安全。所以目前我正在使用 Parse Android SDK 客户端和 Parse PHP SDK 服务器端。我已经能够创建有关解析的用户、登录用户和列表用户端点。您提到的“成为”能力确实帮助我解决了用户将会话令牌发送到服务器并尝试验证它的问题,因为只有当前用户可以查看他们自己的会话信息。我在 PHP 文档中找不到任何提及它的内容,但显然它是可用的,您只需执行以下操作。如果您有任何想法或意见可以使这变得更好,我愿意接受。

DBHandlerParse 类摘录:

   /**
     * Validating user api key
     * If the api key is there in db, it is a valid key
     * @param String $api_key user api key
     * @return boolean
     */
    public function isValidApiKey($api_key) {
        $query = ParseUser::query();
        $query->equalTo("apiKey",$api_key);
        $results = $query->find();

        if(count($results) > 0){
            return true;
        } else{
            return false;
        }
    }

    /**
     * Validating user session token
     * If the session token matches the api key user, it is a valid token
     * @param String $session_token user session token
     * @param String $api_key user api key
     * @return boolean
     */
    public function isValidSessionToken($session_token, $api_key) {

        // Using already validated $api_key, obtain corresponding user object
        $query = ParseUser::query();
        $query->equalTo("apiKey",$api_key);
        $results = $query->find();

        if(count($results) > 0){
            $userObj = $results[0];
        } else{
            return FALSE;
        }

        try{
            // Become user that has this session token
            // Only way to query back the user that they are
            // If no user is found with this token, parse error
            $thisUser = ParseUser::become($session_token);

            $query = ParseSession::query();
            $query->equalTo("user", $userObj);
            $results = $query->find();

            if(count($results) > 0){
                return TRUE;
            } else{
                return FALSE;
            }
        } catch (Parse\ParseException $error){
            return FALSE;
        }
    }

身份验证中间件:

/**
 * Adding Middle Layer to authenticate every request
 * Checking if the request has valid api key in the 'Authorization' & 'Token' header
 */
function authenticate(\Slim\Route $route) {
    // Getting request headers
    $headers = apache_request_headers();
    $response = array();
    $app = \Slim\Slim::getInstance();

    // Verifying Authorization Header
    if (isset($headers['Authorization']) && isset($headers['Token'])) {
        $db = new DbHandlerParse();

        // get the api key
        $api_key = $headers['Authorization'];
        // get the session token
        $session_token = $headers['Token'];

        // validating api key
        if (!$db->isValidApiKey($api_key)) {
            // api key is not present in users table
            $response["result"] = "error";
            $response["message"] = "Access Denied. Invalid Api key";
            echoRespnse(401, $response);
            $app->stop();
        } else if(!$db->isValidSessionToken($session_token, $api_key)) {
            // session token does not match api key or is just invalid
            $response["result"] = "error";
            $response["message"] = "Access Denied. Invalid Token";
            echoRespnse(401, $response);
            $app->stop();
        } else {
            global $user_id;
            // get user primary key id
            $userID = $db->getUserId($api_key);
            if (NULL != $userID)
                $user_id = $userID;
        }
    } else if(!isset($headers['Authorization'])){
        // api key is missing in header
        $response["result"] = "error";
        $response["message"] = "Api key is misssing";
        echoRespnse(400, $response);
        $app->stop();
    } else {
        // token is missing in header
        $response["result"] = "error";
        $response["message"] = "Token is misssing";
        echoRespnse(400, $response);
        $app->stop();
    }
}

示例路线:

/**
 * Users List
 * url - /list
 * method - GET
 */
$app->get('/users/list', 'authenticate', function() use ($app) {
    $response = array();

    $db = new DbHandlerParse();
    $results = $db->getUserList();
    $records = array();

    //echo "Successfully retrieved " . count($results) . " scores.<br><br>";
    // Do something with the returned ParseObject values
    for ($i = 0; $i < count($results); $i++) {
        $object = $results[$i];

        $record = array();
        $records[$i]['userId'] = $object->getObjectId();
        $records[$i]['firstName'] = $object->get('firstName');
        $records[$i]['lastName'] = $object->get('lastName');
        $records[$i]['username'] = $object->get('username');
        $records[$i]['email'] = $object->get('email');

        //echo $object->getObjectId() . ' - ' . $object->get('username') . '<br>';
    }

    // check for records returned
    if ($records) {
        $response["result"] = "success";
        $response['message'] = count($records)." users found.";
        $response['items'] = $records;

    } else {
        // no records found
        $response['result'] = 'success';
        $response['message'] = 'No Users Found';
    }

    echoRespnse(200, $response);
});

【讨论】:

  • 有趣的是 PHP 文档中没有提到它。我只是模糊地熟悉 PHP 语法,但我认为你所做的与我见过的任何方法一样好。我实际上已经从 Parse 切换了。我的逻辑在应用程序中变得过于复杂,因此我添加了一个相当简单的 API 层,其中包含 Sails.js 和 Mongo 数据库。感谢您的投入和详尽的解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-16
  • 1970-01-01
  • 1970-01-01
  • 2017-05-03
  • 2011-07-19
相关资源
最近更新 更多