`

我的第一个PHP程序——实现网站的模拟登录

阅读更多

      我学过一年多的C#和两年多的java,脚本语言一直没怎么写过。因为项目需要,写了一个PHP的模拟登录程序,算是我在原公司做的最后一点贡献了。

 

      程序写的很费劲,花了几天的时间,靠百度,google,zend studio和PHP Mysql的一本书撑下来了。

 

      模拟登录不是真正意义上的单点登录,它又叫做伪单点登录,应用漫游,代填口令等等。

 

      场景是这样的:用户访问一个应用,比如说新浪邮件,首次访问时,应用会提示输入新浪的用户名和密码。保存后,将用户名和密码填充到动态表单,使用动态生成的表单提交,从而实现自动登录。

 

      如果用户修改了新浪里的密码,则提交会失败。这就有一个问题,需要先用指定的用户名和密码尝试登录,如果尝试登录成功,则post;如果不成功,则重定向到录入界面,直到提交成功。

 

      写这个程序的关键点在两个地方,一个而是尝试登录程序,一个是动态表单生成程序。

 

      关键问题找到了,首先我们考虑第一个问题。要模拟浏览器尝试登录,要有一个类似java里的Apache HttpClient的东西。我google很长时间,找到了一个php的HttpClient。开始我艰难的尝试之旅。

 

      分析http请求相关信息,我用的是HttpWatch。

 

     

 

      参考php HttpClient说明,很快我写了一段代码,用来尝试登录。尝试了n次,也没有成功。一开始我是直接post的,因为session id是通过cookie传递的,因为没有session信息,所以总是不成功,真土啊,对http协议理解不够深,相当于补课了。问了有此方面经验的同事,他说一般有两种,一种是使用HttpClient,一种是用httpwatch分析后,把cookie值抄下来,然后尝试登录。怎么想第二种方法都是太土了,但第一种方式又实现不了。

      我又开始google,发现都是介绍java的httpclient的,php的几乎没有。但这次google有一个发现,就是apache的httpclient可以保存多次请求的信息,比如cookie,有记忆的。而我找的HttpClient只能单次请求,也就是说,每次都要手动设置cookie。我突然有想到,我何不从登录页面开始呢?这样我从登录页面开始get,然后将得到的cookie,再set一遍,然后开始post。

      还是不行!

      我开启了这个Http类的debug,发现post之后,重定向得到bad request,原来是重定向得到的path,没有“/”,这是原作者的一个bug。还一个问题比较大,就是他是重定向到另外的域,如果我还用之前设置的域,访问路径完全是错的。这样我在HttpClient里增加了两个参数,一个是判断是否重定向到其它域,如果重定向到其它域。把another_host赋给当前的host。

      终于成功了!

      我通过取回内容title中的值来判断是否登录成功,并把这些配置到数据库中。下面是我的数据库模型图:

 

 

    动态表单生成就比较简单了,直接拼凑html:

 

<?php
require_once 'mysql.connect.php';
require_once 'AppBean.class.php';

/**
 * 生成动态表单
 * author 张永吉(gurudk)
 */
class DynamicForm {
	
	var $appid;
	var $loginUrl;
	var $host;
	var $cookieHost;
	var $port;
	var $formUserName;
	var $formPassword;
	var $errorInfo;
	var $appProperties = array();
	var $appBean;
	var $loginUserValue;
	var $loginPassValue;
	
	public function __construct($appid) {
		$this->appBean = new AppBean($appid);
		$this->loginUrl = $this->appBean->loginUrl;
		$this->host = $this->appBean->host;
		$this->cookieHost = $this->appBean->cookieHost;
		$this->port = $this->appBean->port;
		$this->formUserName = $this->appBean->formUserName;
		$this->formPassword = $this->appBean->formPassword;
		$this->errorInfo = $this->appBean->errorInfo;
		$this->appProperties = $this->appBean->appProperties;
	}
	
	public function __destruct(){

	}
	
	/**
	 * 生成html
	 * document.dynamicform.submit()
	 * @return unknown
	 */
	public function generate() {
		$content = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">".
				"<html>".
					"<head>".
						"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">".
						"<title>Insert title here</title>".
					"</head>".
				"<body onload='document.dynamicform.submit()'>".
		 		"<form name=\"dynamicform\" method=\"POST\" action=\"".$this->loginUrl."\" >".
				$this->generateMultibox().
				$this->generateUserPasswordControl().
		 		"  		<input type=\"submit\" name=\"btnlogin\" value=\"login\">".
				   "</form>".
			 	"</body>".
				"</html>";
		
		return $content;
	}
	
	/**
	 * 生成下拉框或者radio组选框
	 *
	 * @return unknown
	 */
	private function generateMultibox(){
		$i = 0;
		$html = "";
		while($key = key($this->appProperties)){
			if("combox" == $this->appProperties[$key]["type"]){
				$html = "<select id=\"freeselect\" name=\"".$this->appProperties[$key]["key"]."\">";
				
				$values = split(",", $this->appProperties[$key]["value"]);
				do{
					//对于下拉框,value中可接受逗号间隔的列表,第一个值为选中值。
					if($i == 0){
						$html = $html."<option value=\"".current($values)."\" selected=\"selected\">".current($values)."</option>";
					}else{
						$html = $html."<option value=\"".current($values)."\">".current($values)."</option>";
					}
					$i++;
				}while(next($values));
				
				$html = $html."</select>";
			}else if("hidden" == $this->appProperties[$key]["type"]){
				$html .= "<input type='hidden' name=\"".$this->appProperties[$key]["key"]."\" value=\"".$this->appProperties[$key]["value"]."\">";
			}
			
			next($this->appProperties);
		}
		
		return $html;
	}
	
	/**
	 * 生成用户名和密码输入框
	 *
	 * @return unknown
	 */
	private function generateUserPasswordControl(){
		$html = "		<input type='hidden' name=\"".$this->formUserName."\" value=\"".$this->loginUserValue."\">".
		 		"  		<input type=\"password\" name=\"".$this->formPassword."\" value=\"".$this->loginPassValue."\" />";
		
		return $html;
	}

	/**
	 * 填充用户名和密码表单值
	 *
	 * @param unknown_type $userValue
	 * @param unknown_type $passValue
	 */
	public function setAutoLoginValue($userValue, $passValue){
		$this->loginUserValue = $userValue;
		$this->loginPassValue = $passValue;	
	}
}

?>

 

 

尝试登录的代码:

 

 

<?php

require_once 'HttpClient.php';
require_once 'AppBean.class.php';

/**
 * 测试当前用户名和密码是否可以登录远程服务器
 * 
 * author 张永吉(gurudk)
 */
class LoginTry {
	
	var $appBean;
	var $browser;
	var $userName;
	var $password;
	
	/**
	 * 
	 */
	function __construct($appid, $userName, $password) {
		$this->appBean = new AppBean ( $appid );
		$this->userName = $userName;
		$this->password = $password;
			
		$this->setUpBrowser();
	}
	
	/**
	 * 
	 */
	function __destruct() {
		
	//TODO - Insert your code here
	}
	
	private function setUpBrowser() {
		$this->browser = new HttpClient ( $this->appBean->host, $this->appBean->port );
		$client->max_redirects = 10;
		$this->browser->cookie_host = $this->appBean->cookieHost;
		$this->browser->setUserAgent ( 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)' );
	}
	
	/**
	 * 对指定网站进行尝试登录
	 *
	 */
	public function tryLogin() {
		if($this->getTitle() == $this->appBean->errorInfo){
			return false;
		}
		
		return true;
	}
	
	public function getTitle(){
		$this->browser->get ( "/" );
		$this->browser->setCookies ( $this->browser->getCookies () );
		$this->browser->redirect_to_other_site = $this->appBean->isRedirectOther;
		$this->browser->another_host = $this->appBean->anotherHost;
		$this->browser->post ( $this->appBean->postUrl, $this->buildPostParams());
		$this->browser->getContent ();
		preg_match_all("/<title>(.*)<\/title>/i", $this->browser->getContent (), $matches);
		$result = $matches[1][0];
		
		//编码转换
		if("utf-8" == $this->appBean->encode){
			$title = iconv("utf-8", "gb2312", $result);
		}else{
			$title = $result;
		}		
		
		return $title;
	}
	
	/**
	 * 读取数据库数据,构造post参数array
	 *
	 */
	public function buildPostParams(){
		$params = array ($this->appBean->formUserName => $this->userName, $this->appBean->formPassword => $this->password );
		while($key = key($this->appBean->appProperties)){
			$this->copyOtherParams($params, $this->appBean->appProperties[$key]);
			next($this->appBean->appProperties);
		}
		
		return $params;
	}

	/**
	 * copy除用户名,密码外其它需要配置的参数,在数据库应用属性表中进行配置
	 *
	 * @param unknown_type $params
	 * @param unknown_type $propArray
	 */
	private function copyOtherParams(&$params, $propArray){
		$controlType = $propArray["type"];
		if("combox" == $controlType || "radio" == $controlType){
			$params[$propArray["key"]] = current(split(",", $propArray["value"]));
		}else{
			$params[$propArray["key"]] = $propArray["value"];
		}
	}
}

?>

       

 

用这种做法,登录新浪邮件是没问题的,但是登录网易时,我使用localhost是可以的,使用ip地址和其它域名不行。

可能网易邮件会验证refer地址,但验证refer地址的算法有点问题,:)。

 

 

分享到:
评论

相关推荐

    Google Android SDK开发范例大全(完整版)

    2.2 建立第一个Android项目(HelloAndroid!) 2.3 Android应用程序架构——从此开始 2.4 可视化的界面开发工具 2.5 部署应用程序到Android手机 第3章 用户人机界面 3.1 更改与显示文字标签——TextView标签的使用 ...

    PHP开发实战1200例源码

    实例003 第1个PHP程序 7 1.2 XAMPP——PHP集成化安装包 8 实例004 通过XAMPP配置PHP开发环境 8 实例005 测试XAMPP是否安装成功 11 实例006 XAMPP应用技巧 12 实例007 第2个PHP程序 13 1.3 IIS+PHP+MySQL——独立搭建...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part1

    实例028 通过Dreamweaver开发第1个PHP程序 48 1.7 Zend Studio开发工具 50 实例029 安装Zend Studio 50 实例030 Zend Studio创建PHP项目 52 实例031 Zend Studio编码格式的转换 56 实例032 Zend Studio中快捷键的...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part2

    实例028 通过Dreamweaver开发第1个PHP程序 48 1.7 Zend Studio开发工具 50 实例029 安装Zend Studio 50 实例030 Zend Studio创建PHP项目 52 实例031 Zend Studio编码格式的转换 56 实例032 Zend Studio中快捷键的...

    JAVA上百实例源码以及开源项目源代码

     当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket。  QQ客户端登录界面,中部有三个JPanel,有一个叫选项卡窗口管理。还可以更新...

    php飞信 pafetion 开源

    to 接收者的标志 (可以是手机号 也可以是昵称 ,昵称如果有重复的默认发第一个的)(注意:不能给自己发送) (不和v1兼容) msg 消息的正文(默认gbk 编码) (-v1) (可选) u 是否使用utf8编码(默认的编码是gbk , 此参数...

    若干源程序资料12.rar

    2012-06-11 21:44 2,279 C语言编一个程序完成64位数据(无符号)的加法,减法运算.txt 2012-06-11 21:43 1,480,155 Direct3D加载3d文件.rar 2012-06-11 21:29 22,102 DSP编程一周通.rar 2012-06-11 21:04 837,926 ...

    JAVA上百实例源码以及开源项目

     当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket。  QQ客户端登录界面,中部有三个JPanel,有一个叫选项卡窗口管理。还可以更新...

    Google Android SDK开发范例大全(第3版) 1/5

    2.2 创建第一个Android项目(Hello Android!) 2.3 Android应用程序架构——从此开始 2.4 可视化的界面开发工具 2.5 部署应用程序到Android手机 第3章 用户人机界面 3.1 更改与显示文字标签 3.2 更改手机窗口画面底色 ...

    Google Android SDK开发范例大全(第3版) 4/5

    2.2 创建第一个Android项目(Hello Android!) 2.3 Android应用程序架构——从此开始 2.4 可视化的界面开发工具 2.5 部署应用程序到Android手机 第3章 用户人机界面 3.1 更改与显示文字标签 3.2 更改手机窗口画面底色 ...

    Google Android SDK开发范例大全(第3版) 3/5

    2.2 创建第一个Android项目(Hello Android!) 2.3 Android应用程序架构——从此开始 2.4 可视化的界面开发工具 2.5 部署应用程序到Android手机 第3章 用户人机界面 3.1 更改与显示文字标签 3.2 更改手机窗口画面底色 ...

    Google Android SDK开发范例大全(第3版) 5/5

    2.2 创建第一个Android项目(Hello Android!) 2.3 Android应用程序架构——从此开始 2.4 可视化的界面开发工具 2.5 部署应用程序到Android手机 第3章 用户人机界面 3.1 更改与显示文字标签 3.2 更改手机窗口画面底色 ...

    考研复试准备以及408相关内容

    这个项目是我在2021年考研时的刷题集锦,其中包括王道机试指南第二版以及杭电OJ,前期用的C语言,后续改用C++,感谢作者炉灰 王道考研机试指南第2版——题目链接、代码 题解(书籍作者炉灰):例题代码 习题代码 ...

    正则表达式经典实例中文版 (美)高瓦特斯

    第1章 正则表达式简介 1.1 正则表达式的定义 1.2 使用正则表达式的工具 第2章 正则表达式的基本技巧 2.1 匹配字面文本 2.2 匹配不可打印字符 2.3 匹配多个字符之 2.4 匹配任意字符 2.5 匹配文本行起始和/或文本行...

    [完整][中文][WEB安全测试].(美)霍普.扫描版.pdf

    第1章 绪论 13 1.1 什么是安全测试 13 1.2 什么是Web应用 17 1.3 Web应用基础 21 1.4 Web应用安全测试 25 1.5 方法才是重点 26 第2章 安装免费工具 29 2.1 安装Firefox 29 2.2 安装Firefox扩展 30 2.3 安装Firebug ...

    精通正则表达式~~~

    第1章:正则表达式入门.... 1 解决实际问题... 2 作为编程语言的正则表达式... 4 以文件名做类比... 4 以语言做类比... 5 正则表达式的知识框架... 6 对于有部分经验的读者... 6 检索文本文件:Egrep. 6 ...

Global site tag (gtag.js) - Google Analytics