<span style="font-family:SimSun;"><span style="font-size:12px;"><span style="line-height: 21px;"></span></span></span>
最近项目需求要了解owncloud这个开源的软件。来自开源中国上的介绍wnCloud 是一个来自 KDE 社区开发的免费软件,提供私人的 Web 服务。当前主要功能包括文件管理(内建文件分享)、音乐、日历、联系人等等,可在PC和服务器上运行。简单来说就是一个基于Php的自建网盘。基本上是私人使用这样,因为直到现在开发版本也没有暴露注册功能。
现在分析一下登录过程,因为看了好久的源码,里边全都是PHP面向对象的写法,自己是个菜鸟,看不懂,只能蒙着找。大概理请了一下思路:
1.首先从index.php着手,因为我们用浏览器访问目录的时候默认就是这个。看一下源码:
<?php
/**
* @author Frank Karlitschek <frank@owncloud.org>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@owncloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <icewind@owncloud.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <pvince81@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
// Show warning if a PHP version below 5.4.0 is used, this has to happen here
// because base.php will already use 5.4 syntax.
if (version_compare(PHP_VERSION, '5.4.0') === -1) {
echo 'This version of ownCloud requires at least PHP 5.4.0<br/>';
echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.';
return;
}
try {
require_once 'lib/base.php';
OC::handleRequest();
} catch(\OC\ServiceUnavailableException $ex) {
\OCP\Util::logException('index', $ex);
//show the user a detailed error page
OC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE);
OC_Template::printExceptionErrorPage($ex);
} catch (\OC\HintException $ex) {
OC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE);
OC_Template::printErrorPage($ex->getMessage(), $ex->getHint());
} catch (Exception $ex) {
\OCP\Util::logException('index', $ex);
//show the user a detailed error page
OC_Response::setStatus(OC_Response::STATUS_INTERNAL_SERVER_ERROR);
OC_Template::printExceptionErrorPage($ex);
}
看到上边的代码主要是在这两句代码。其中包含了一个lib里边的base.php文件类,这里边是创建的一个OC类,里边 有很多功能,看的不是很明白,里边大概是一些初始化的内容,类里边的具体代码就不粘贴了,base.php里边会自己初始化类里边的一些函数,重点是index.php里边如何加载出来页面的。这里边是用到了框架,具体是啥框架没看出来。
require_once 'lib/base.php';
OC::handleRequest();
但是用到了模版,登录界面的模版login.php是在/core/templates/login.php。打开这个文件我们就可以看到登录页面了,但是发现这里边采用表单方式提交用户信息,可是表单里边却没有action选项,这就意味着表单提交的时候在将数据提交在本页,因为采用POST方法提交,我们就可以在index本页利用$_POST[]方式来获取数据。当时不知道数据提交在哪,找了好久发现就是用户将信息输入后,点击提交,将用户名密码都提交到了inde.php本页。代码再获取就可以了。
现在说一下如何加载的模版:主要方法在OC::handleRequest()函数中,我们看一下源码:
/**
* Handle the request
*/
public static function handleRequest() {
\OC::$server->getEventLogger()->start('handle_request', 'Handle request');
$systemConfig = \OC::$server->getSystemConfig();
// load all the classpaths from the enabled apps so they are available
// in the routing files of each app
OC::loadAppClassPaths();
// Check if ownCloud is installed or in maintenance (update) mode
if (!$systemConfig->getValue('installed', false)) {
\OC::$server->getSession()->clear();
$setupHelper = new OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults());
$controller = new OC\Core\Setup\Controller($setupHelper);
$controller->run($_POST);
exit();
}
$request = \OC::$server->getRequest()->getPathInfo();
if (substr($request, -3) !== '.js') { // we need these files during the upgrade
self::checkMaintenanceMode();
self::checkUpgrade();
}
// Always load authentication apps
OC_App::loadApps(['authentication']);
// Load minimum set of apps
if (!self::checkUpgrade(false)
&& !$systemConfig->getValue('maintenance', false)
&& !\OCP\Util::needUpgrade()) {
// For logged-in users: Load everything
if(OC_User::isLoggedIn()) {
OC_App::loadApps();
} else {
// For guests: Load only filesystem and logging
OC_App::loadApps(array('filesystem', 'logging'));
\OC_User::tryBasicAuthLogin();
}
}
if (!self::$CLI and (!isset($_GET["logout"]) or ($_GET["logout"] !== 'true'))) {
try {
if (!$systemConfig->getValue('maintenance', false) && !\OCP\Util::needUpgrade()) {
OC_App::loadApps(array('filesystem', 'logging'));
OC_App::loadApps();
}
self::checkSingleUserMode();
OC_Util::setupFS();
OC::$server->getRouter()->match(\OC::$server->getRequest()->getRawPathInfo());
return;
} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
//header('HTTP/1.0 404 Not Found');
} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
OC_Response::setStatus(405);
return;
}
}
// Handle redirect URL for logged in users
if (isset($_REQUEST['redirect_url']) && OC_User::isLoggedIn()) {
$location = OC_Helper::makeURLAbsolute(urldecode($_REQUEST['redirect_url']));
// Deny the redirect if the URL contains a @
// This prevents unvalidated redirects like ?redirect_url=:user@domain.com
if (strpos($location, '@') === false) {
header('Location: ' . $location);
return;
}
}
// Handle WebDAV
if ($_SERVER['REQUEST_METHOD'] == 'PROPFIND') {
// not allowed any more to prevent people
// mounting this root directly.
// Users need to mount remote.php/webdav instead.
header('HTTP/1.1 405 Method Not Allowed');
header('Status: 405 Method Not Allowed');
return;
}
// Redirect to index if the logout link is accessed without valid session
// this is needed to prevent "Token expired" messages while login if a session is expired
// @see https://github.com/owncloud/core/pull/8443#issuecomment-42425583
if(isset($_GET['logout']) && !OC_User::isLoggedIn()) {
header("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));
return;
}
// Someone is logged in
if (OC_User::isLoggedIn()) {
OC_App::loadApps();
OC_User::setupBackends();
OC_Util::setupFS();
if (isset($_GET["logout"]) and ($_GET["logout"])) {
OC_JSON::callCheck();
if (isset($_COOKIE['oc_token'])) {
\OC::$server->getConfig()->deleteUserValue(OC_User::getUser(), 'login_token', $_COOKIE['oc_token']);
}
OC_User::logout();
// redirect to webroot and add slash if webroot is empty
header("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));
} else {
// Redirect to default application
OC_Util::redirectToDefaultPage();
}
} else {
// Not handled and not logged in
self::handleLogin();
}
}
以上的代码大部分都是用来验证登录和设置一些其他的东西,就是登录了会做一些事情,没有登录会做一些事情。太复杂了,经过调试,发现登录页面载入就是最后一句代码,我们从注释也能看出,没有处理和没有登录的话 调用类本身的的handleLoin()方法。这里边的调用层级比较多,耐心点吧。接下来看一下这个函数(看源码建议用IDE工具,可以直接找到定义的函数位置,之前傻呼呼的用记事本看。。。真心傻)
protected static function handleLogin() {
OC_App::loadApps(array('prelogin'));
$error = array();
$messages = [];
try {
// auth possible via apache module?
if (OC::tryApacheAuth()) {
$error[] = 'apacheauthfailed';
} // remember was checked after last login
elseif (OC::tryRememberLogin()) {
$error[] = 'invalidcookie';
} // logon via web form
elseif (OC::tryFormLogin()) {
$error[] = 'invalidpassword';
}
} catch (\OC\User\LoginException $e) {
$messages[] = $e->getMessage();
} catch (\Exception $ex) {
\OCP\Util::logException('handleLogin', $ex);
// do not disclose information. show generic error
$error[] = 'internalexception';
}
OC_Util::displayLoginPage(array_unique($error), $messages);
}
这个函数就比较清爽了,try里边有三层验证,先判断是否apache登录,其次判断是否cookie登录,最后判断是否是表单登录,因为我们要做的是单点登录,绕过owncloud的登录界面,所以要修改表单的提交方式。接下来代码是有异常处理异常,最后将模版页面打印出来调用的函数是
OC_Util::displayLoginPage(array_unique($error), $messages);
上边的逻辑就是如果在if判断中用户成功登录,则跳转到相应的页面,就不会继续执行了(要看相应的判断登录函数,继续一层一层找下去)。如果登录失败出错,则返回设置$error[]数组信息,就会继续执行
OC_Util::displayLoginPage(array_unique($error), $messages);
这句代码。效果就是登录成功就跳转到主页面,如何登录失败就会跳转在登录页面。因为我们要自定义页面,所以在这里将以上代码屏蔽掉。添加一句跳转语句:
header(“Location:http://xxxxx.php”);这里添加我们自己要跳转的页面。
接下来说说如何从我们自己的登录页面传值过去,现在有两种方式:一种是POST过去,通过AJAX也好,表单提交也好,直接向index.php页面传递就好,但是POST过后,不知道怎么能直接进入主页面,就是用户名密码都传递过去,验证通过他会自动跳转,但是从A页面POST值过去到index.php页面后,还是留在A页面不会跳转进去;所以我用了方法2:采用GET方法,执行url+user=”admin”+password=”abc”,直接访问这个页面就可以跳转了。如果验证成功就可以进去,如果验证失败的话,就会跳转到当前登录页面。这里要重新写一个GET登录方法,其实就是将tryFormLogin()方法全都复制过来,将里边的帐号密码获取的变量由 $_POST[]改为$_GET[]接收方式就可以了。如下:也是写在base.php里边的。
protected static function tryGetLogin() {
if (!isset($_GET["user"]) || !isset($_GET['password'])) {
return false;
}
/*if(!OC_Util::isCallRegistered()) {
return false;
}*/
OC_App::loadApps();
//setup extra user backends
OC_User::setupBackends();
if (OC_User::login((string)$_GET["user"], (string)$_GET["password"])) {
$userId = OC_User::getUser();
// setting up the time zone
if (isset($_POST['timezone-offset'])) {
self::$server->getSession()->set('timezone', (string)$_POST['timezone-offset']);
self::$server->getConfig()->setUserValue($userId, 'core', 'timezone', (string)$_POST['timezone']);
}
self::cleanupLoginTokens($userId);
if (!empty($_POST["remember_login"])) {
if (defined("DEBUG") && DEBUG) {
self::$server->getLogger()->debug('Setting remember login to cookie', array('app' => 'core'));
}
$token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(32);
self::$server->getConfig()->setUserValue($userId, 'login_token', $token, time());
OC_User::setMagicInCookie($userId, $token);
} else {
OC_User::unsetMagicInCookie();
}
OC_Util::redirectToDefaultPage();
exit();
}
return true;
}
接下来说一下,GET过来后,登录验证有个地方过不去,是因为owncloud里边采用了很多安全性的验证:下边的代码会进行是否是跨站脚本攻击,具体是如何运作的,我也没看懂,但是屏蔽掉了就可以通过了,可能是从其他地方GET过去请求,认为是跨站脚本攻击吧。
/*if(!OC_Util::isCallRegistered()) {
return false;
}*/
登录这部分看的不是很细 ,先记录下来,大概就这样实现了功能。