我的收藏

单点登录(SSO)的实现—通行证的基本原理

问题起源:想做一个面向校园的网站,因为势单力薄。部分模块采用整合其它开源系统的方案,比如BBS系统和BLOG系统。首先面临的就是用户身份认证的方式。由于这些不是自己开发的系统,都分别有自己的用户系统,于是面临统一身份认证的过程。

以前看过企业级的Web service方案,主要是通过XML,SOAP,WSDL和UDDI来实现。将应用服务都注册到UDDI服务器中,通过SOAP协议使用XML传递信息(当然需经过加密)。由于涉及到很多服务部署的问题,用JAVA来做这样的项目肯定是再好不过的了。我的目的只是几个WEB系统的整合,肯定是要排除这么伟大的方案了。关于Web service有兴趣的朋友可以参考机械工业出版社出版的《Web Servides原理与研发实践》,里面有详细的介绍。

那么对于这样的小WEB系统的整合该怎么来实现?我们假设这是个从零开始的项目,除了自己开发的系统外,还要用到一些其它组织开发的开源系统:比如 BLOG,CMS,BBS。这些系统都有各自的用户系统。要把他们整合到一块有个原则,就是尽量不要破坏或者修改这些系统。那么要实现统一身份认证,我们必须要有一个用户信息库,然后吧这个数据库的信息映射到那些子系统的数据库中,在大型项目中,一般都会独立出一台单独的用户信息服务器,大部分高校采用 LDAP来存放用户信息,因为采用的是树状结构,对经常读取但很少修改的数据而言,它的性能是很高的。

LDAP用户库和各子系统用户库的映射有很多种方法,我这里只用最简单的直接映射,也就是帐号和密码都是相同的。

假设我的域名部署如下

http://news.domain.com 这是CMS系统的域名

http://bbs.domain.com 这是论坛的域名

http://blog.domain.com 这是博客域名

http://reg.domain.com 这是统一注册和登录页面的域名
首先是注册,我们让所有子系统注册页面都转向到一个注册页面上来(各种脚本语言都有转向函数),比如说当用户希望在http://blog.domain.com/reg.php注册是,reg.php把这个请求转向到http://reg.domain.com/reg.php.

在实现注册时,由于刚开始时候子系统并不多,注册时把用户注册信息写入主用户数据库的同时写入各子系统的用户库。以后若有新的子系统加入进来时可以通过帐号激活的方式来实现新系统的帐号激活。

用户登录的过程,可以参考如下来自IBM的图片


流程描述如下:(仅描述正常流程)

1. 用户使用在统一认证服务注册的用户名和密码(也可能是其他的授权信息,比如数字签名等)登陆统一认证服务;

2. 统一认证服务创建了一个会话,同时将与该会话关联的访问认证令牌返回给用户;

3. 用户使用这个访问认证令牌访问某个支持统一身份认证服务的应用系统;

4. 该应用系统将访问认证令牌传入统一身份认证服务,认证访问认证令牌的有效性;

5. 统一身份认证服务确认认证令牌的有效性;

6. 应用系统接收访问,并返回访问结果,如果需要提高访问效率的话,应用系统可选择返回其自身的认证令牌已使得用户之后可以使用这个私有令牌持续访问。

上面所说的令牌我在WEB引用中可以用COOKIE或者SEESION来实现。

例如通过COOKIE来实现

1.用户在统一登录页登陆,通过查询主用户数据库判断用户是否合法,若是,则注册该用户的唯一COOKIE标识(可以通过加密用户名和密码得到,网上有很多算法)。

2.用户进入某子系统时,先判断COOKIE是否注册,若注册了,则解密该COOKIE得到用户名和帐号,并判断合法性。
3.如果合法,则立刻在该子系统中注册(可在原子系统的登录脚本种抽出登录的部分做成一个函数)

使用COOKIE的优点就是简单,只要设置一下就可以实现COOKIE的跨子域传递

例如

setcookie(NC_USER_COOKIE, 用户名, 失效时间, 作用路径, ‘.domain.com’);

就能实现在所有.domain.com子域下的传递。

但COOKIE也有他的缺点,首先就是安全级别不高,要提防COOKIE劫持的威胁,其次就是它只能跨子域传递,而不能跨完全不同的域。比如说 domain.com和fuck.com之间就不能传递。

然后再说下SESSION的方式,由于SESSION是存储在服务器端的,所以安全级别肯定要比COOKIE的级别高。但是由于SEESION存储的位置不同,造成了无法跨域传递。可以通过把SEESION村入数据库来解决这个问题。

由于PHP的SESSION需要用到标识SESSION的COOKIE,所以需要设置下COOKIE的作用域
ini_set(’session.cookie_domain’, ‘.domian.com’);

然后重写PHP的SESSION操作函数。

PHP 提供了session_set_save_handle() 函数来自定义 SESSION 的处理过程,先将 session.save_handler 改成 user

session_module_name(‘user’);

然后几可以重写SESSION的操作了,下面是PHP牛人NIO写的SESSION操作

define(‘MY_SESS_TIME’, 3600);    //SESSION 生存时长
//类定义
class My_Sess
{
     function init()
     {
         $domain = ‘.infor96.com’;
         //不使用 GET/POST 变量方式
         ini_set(’session.use_trans_sid’,     0);
         //设置垃圾回收最大生存时间
         ini_set(’session.gc_maxlifetime’,    MY_SESS_TIME);

         //使用 COOKIE 保存 SESSION ID 的方式
         ini_set(’session.use_cookies’,       1);
         ini_set(’session.cookie_path’,       ‘/’);
         //多主机共享保存 SESSION ID 的 COOKIE
         ini_set(’session.cookie_domain’,     $domain);

         //将 session.save_handler 设置为 user,而不是默认的 files
         session_module_name(‘user’);
         //定义 SESSION 各项操作所对应的方法名:
         session_set_save_handler(
             array(‘My_Sess’, ‘open’),    //对应于静态方法 My_Sess::open(),下同。
             array(‘My_Sess’, ‘close’),
             array(‘My_Sess’, ‘read’),
             array(‘My_Sess’, ‘write’),
             array(‘My_Sess’, ‘destroy’),
             array(‘My_Sess’, ‘gc’)
         );
     }    //end function
     function open($save_path, $session_name) {
         return true;
     }    //end function

     function close() {
         global $MY_SESS_CONN;

         if ($MY_SESS_CONN) {     //关闭数据库连接
             $MY_SESS_CONN->Close();
         }
         return true;
     }    //end function

     function read($sesskey) {
         global $MY_SESS_CONN;

         $sql = ‘SELECT data FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey) . ‘ AND expiry>=’ . time();
         $rs =& $MY_SESS_CONN->Execute($sql);
         if ($rs) {
             if ($rs->EOF) {
                 return ‘’;
             } else {     //读取到对应于 SESSION ID 的 SESSION 数据
                 $v = $rs->fields[0];
                 $rs->Close();
                 return $v;
             }    //end if
         }    //end if
         return ‘’;
     }    //end function

     function write($sesskey, $data) {
         global $MY_SESS_CONN;
      
         $qkey = $MY_SESS_CONN->qstr($sesskey);
         $expiry = time() + My_SESS_TIME;     //设置过期时间
      
         //写入 SESSION
         $arr = array(
             ’sesskey’ => $qkey,
             ‘expiry’   => $expiry,
             ‘data’     => $data);
         $MY_SESS_CONN->Replace(’sess’, $arr, ’sesskey’, $autoQuote = true);
         return true;
     }    //end function

     function destroy($sesskey) {
         global $MY_SESS_CONN;

         $sql = ‘DELETE FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey);
         $rs =& $MY_SESS_CONN->Execute($sql);
         return true;
     }    //end function

     function gc($maxlifetime = null) {
         global $MY_SESS_CONN;

         $sql = ‘DELETE FROM sess WHERE expiry. time();
         $MY_SESS_CONN->Execute($sql);
         //由于经常性的对表 sess 做删除操作,容易产生碎片,
         //所以在垃圾回收中对该表进行优化操作。
         $sql = ‘OPTIMIZE TABLE sess’;
         $MY_SESS_CONN->Execute($sql);
         return true;
     }    //end function
}    ///:~

//使用 ADOdb 作为数据库抽象层。
require_once(‘adodb/adodb.inc.php’);
//数据库配置项,可放入配置文件中(如:config.inc.php)。
$db_type = ‘mysql’;
$db_host = ‘192.168.212.1′;
$db_user = ’sess_user’;
$db_pass = ’sess_pass’;
$db_name = ’sess_db’;
//创建数据库连接,这是一个全局变量。
$GLOBALS[‘MY_SESS_CONN’] =& ADONewConnection($db_type);
$GLOBALS[‘MY_SESS_CONN’]->Connect( $db_host, $db_user, $db_pass, $db_name);
//初始化 SESSION 设置,必须在 session_start() 之前运行!!
My_Sess::init();

基本的原理差不多就这样,细节方面就看还有很多需要琢磨的地方..

如何防止linux服务器被暴力破解密码?

经常有人会利用ssh来暴力破解服务器密码,然后给服务器挂马,查看服务器的安全记录,

打开/var/logs/secure文件,会发现很多利用ssh来暴力破解登录的记录,如下

1: Aug 29 16:27:23 fgb sshd[31098]: Failed password for root from 189.205.132.145 port 49920 ssh2

2: Aug 29 16:27:28 fgb sshd[31100]: Failed password for root from 189.205.132.145 port 55661 ssh2

3: Aug 29 16:27:33 fgb sshd[31103]: Failed password for root from 189.205.132.145 port 33579 ssh2

4: Aug 29 16:27:37 fgb sshd[31106]: Failed password for root from 189.205.132.145 port 39344 ssh2

5: Aug 29 16:27:42 fgb sshd[31115]: Failed password for root from 189.205.132.145 port 45117 ssh2

6: Aug 29 16:27:46 fgb sshd[31124]: Failed password for root from 189.205.132.145 port 50881 ssh2

7: Aug 29 16:27:52 fgb sshd[31126]: Failed password for root from 189.205.132.145 port 56359 ssh2

8: Aug 29 16:27:57 fgb sshd[31128]: Failed password for root from 189.205.132.145 port 35882 ssh2

9: Aug 29 16:28:02 fgb sshd[31130]: Failed password for root from 189.205.132.145 port 41888 ssh2

10: Aug 29 16:28:08 fgb sshd[31132]: Failed password for root from 189.205.132.145 port 47882 ssh2

11: Aug 29 16:28:12 fgb sshd[31134]: Failed password for root from 189.205.132.145 port 53121 ssh2

12: Aug 29 16:28:17 fgb sshd[31136]: Failed password for root from 189.205.132.145 port 59014 ssh2

13: Aug 29 16:28:21 fgb sshd[31139]: Failed password for root from 189.205.132.145 port 36742 ssh2

有人破解,我们肯定要进行防范,使用以下的这段代码:

1: #!/bin/sh

2: SCANIP=`grep “Failed” /var/log/secure | awk ‘{print $(NF-3)}’ |sort|uniq -c|awk ‘{print $1″=”$2;}’`

3: for i in $SCANIP

4: do

5: NUMBER=`echo $i|awk -F= ‘{print $1}’`

6: SCANIP=`echo $i|awk -F= ‘{print $2}’`

7: echo “$NUMBER($SCANIP)”

8: if [ $NUMBER -gt 10 ] && [ -z "`iptables -vnL INPUT|grep $SCANIP`" ]

9: then

10: /sbin/iptables -I INPUT -s $SCANIP -m state –state NEW,RELATED,ESTABLISHED -j DROP

11: echo “`date` $SCANIP($NUMBER)” >> /var/log/scanip.log

12: fi

13: done

这段代码作用是:扫描secure安全日志文件,发现超过10次非法链接的ip,将其列入iptable防火墙禁止列表,并保存在记录文件中。

Mysql postfix socket ‘/var/lib/mysql/mysql.sock’ (2) 错误解决办法

ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’ (2)

1、先查看 /etc/rc.d/init.d/mysqld status 看看m y s q l 是否已经启动.
另外看看是不是权限问题.

2、确定你的mysql.sock是不是在那个位置,
mysql -u 你的mysql用户名 -p -S /var/lib/mysql/mysql.sock

3、试试:service mysqld start

4、如果是权限问题,则先改变权限 #chown -R mysql:mysql /var/lib/mysql

[root@localhost ~]# /etc/init.d/mysqld start
启动 MySQL: [ 确定 ]
[root@localhost ~]# mysql -uroot -p

ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’ (2)
原因是,/var/lib/mysql 的访问权限问题。

shell> chown -R mysql:mysql /var/lib/mysql

接着启动服务器

shell> /etc/init.d/mysql start

服务器正常启动后察看 /var/lib/mysql 自动生成mysql.sock文件。

但是我的问题仍然没有得到解决。

问题终于解决:
方法:    修改/etc/my.conf:
[mysqld]
datadir=/usr/local/mysql/data
socket=/var/lib/mysql/mysql.sock

[mysql.server]
user=mysql
basedir=/usr/local/mysql

If there is not currently a section called [client], add one at the bottom of the file and copy the socket= line under the [mysqld] section such as:
[client]
socket=/var/lib/mysql/mysql.sock

发现依旧如此,运行/etc/init.d/mysql start报错:    Starting MySQLCouldn’t find MySQL manager or server
是mysqld服务没启,运行/usr/local/mysql/bin/mysqld_safe &

学习PHP设计模式20-桥接模式

在面向对象设计的时候,我们一般会根据需要,设计不同的类。但是如果两个类需要类似的功能的时候,我们就会遇到问题,到底重用还是重写。

更复杂的是,如果一些功能需要临时转换就麻烦了,比如星际里面的虫族,地面部队可以钻到地下,然后隐形。

但是小狗在地下不能动,而地刺可以攻击。尽管可以设计不同的类,但问题是玩家可以看到自己的部队埋在地下(一个洞),而敌人看不到。

这涉及功能的切换,而且星际里面有很多探测隐形的东西,这样就更频繁了。

待解决的问题:我们要临时切换一些功能的实现方式,而且在此基础上不同的调用类又有所不同。

思路:将钻地区分两种实现,不同的部队类在它的基础上进一步扩展。

桥接(Bridge)模式示例:

<?php

//虫族的基础类

class Zerg

{

  // 实现钻地的基本对象

  public $imp;

  //负责切换钻地基本对象的方法

  public function setImp($imp)

  {

  $this->imp = $imp;

  }

  //部队的钻地方法,可以扩展基本对象的钻地

  public function underground()

  {

  $this->imp->underground();

  }

}

// 小狗的类

class Zergling extends Zerg

{

  //调用基本的钻地方法,然后实现扩展,这里简单的echo

  public function underground()

  {

    parent::underground();

    echo ‘小狗不能动<br>’;

  }

}

// 地刺的类

class Lurker extends Zerg

{

  //调用基本的钻地方法,然后实现扩展,这里简单的echo

  public function underground()

  {

    parent::underground();

    echo ‘地刺能够进行攻击<br>’;

  }

}

// 钻地的基本接口

interface Implementor

{

  //基本的钻地方法

  public function underground();

}

//隐形钻地的基本类

class InvisibleImp implements Implementor

{

  //基本的钻地方法

  public function underground()

  {

    echo ‘隐形了,什么也看不到<br>’;

  }

}

//不隐形钻地的基本类,比如玩家自己看到的或被探测到的

class VisibleImp implements Implementor

{

  //基本的钻地方法

  public function underground()

  {

    echo ‘地上一个洞<br>’;

  }

}

//造一个小狗

$z1 = new Zergling();

//玩家把它埋入前沿阵地,假设此时有敌人经过,代码进行切换

$z1->setImp(new InvisibleImp());

//敌人看不到小狗,但是小狗也不能进攻

$z1->underground();

// 造一个地刺

$l1 = new Lurker();

//玩家把它埋入前沿阵地,假设此时有敌人经过,代码进行切换

$l1->setImp(new InvisibleImp());

//敌人看不到地刺,但是地刺能攻击敌人

$l1->underground();

//敌人急了,马上飞过来一个科技球,代码进行切换

$l1->setImp(new VisibleImp());

//敌人看到地刺了,地刺继续攻击敌人

$l1->underground();

?>

用途总结:桥接模式可以将基本的实现和具体的调用类分开,调用类可以扩展更复杂的实现。

实现总结:需要一些基本执行类,实现基本的方法,比如上面的两个钻地类。同时我们可以设计多个不同的扩展调用类,将基本的功能扩展,比如地刺和小狗就进一步实现了不同的在地下的行为。

学习PHP设计模式19-组合模式

星际里面我们可以下载别人制作的地图,或者自己做地图玩。

我们在选择玩哪张地图的时候,可以看到游戏列出当前地图包里面的地图或地图包的名字。

虽然地图和地图包是通过文件和文件夹区分的,但是我们开发的时候,总希望能使用对象来进行抽象。

那么对于地图和地图包这两个相关的对象,我们能不能简化他们之间的区别呢?

待解决的问题:尽量是调用这两种对象的代码一致,也就是说很多场合不必区分到底是地图还是地图包。

思路:我们做一个抽象类,让地图类和地图包类继承它,这样类的很多方法的名称一样。

组合(Composite)模式示例:

<?php

//抽象地图类

abstract class abstractMap

{

  //地图或地图包的名称

  public $name;

  //构造方法

  public function __construct($name)

  {

    $this->name = $name;

  }

  //地图或地图包的名称,地图对象没有子对象,所以用空函数,直接继承

  public function getChildren(){}

  //添加子对象,地图对象没有子对象,所以用空函数,直接继承

  public function addChild(abstractMap $child){}

  // 显示地图或地图包的名称

  public function showMapName()

  {

    echo $this->name.”<br>”;

  }

  // 显示子对象,地图对象没有子对象,所以用空函数,直接继承

  public function showChildren(){}

}

// 地图类,继承抽象地图,这里面我们暂且使用抽象地图的方法

class Map extends abstractMap

{

}

//地图包类,继承抽象地图,这里面我们就需要重载抽象地图的方法

class MapBag extends abstractMap

{

  //子对象的集合

  public $childern;

  //添加子对象,强制用abstractMap 对象,当然地图和地图包由于继承了abstractMap,所以也是abstractMap对象

  public function addChild(abstractMap $child)

  {

    $this->childern[] = $child;

  }

  //添加子对象

  public function function showChildren()

  {

    if (count($this->childern)>0)

    {

        foreach ($this->childern as $child)

        {

        //调用地图或包的名称

        $child->showMapName();

        }

    }

  }

}

// 新建一个地图包对象,假设文件夹名字为Allied,这个大家可以看看星际的地图目录,真实存在的

$map1 = new MapBag(‘Allied’);

//新建一个地图对象,假设文件名字为(2)Fire Walker(也是真实的)

$map2 = new Map(‘(2)Fire Walker’);

//接下去可以看到组合模式的特点和用处。

//假设后面的代码需要操作两个对象,而我们假设并不清楚这两个对象谁是地图,谁是地图包

//给$map1添加一个它的子对象,是个地图,(4)The Gardens

$map1->addChild(new Map(‘(4)The Gardens’));

//展示它的子对象

$map1->showChildren();

//给$map2 添加一个它的子对象,是个地图,(2)Fire Walker,这里不会报错,因为地图继承了一个空的添加方法

$map2->addChild(new Map(‘(2)Fire Walker’));

// 展示它的子对象,也不会出错,因为地图继承了一个空的展示方法

$map2->showChildren();

?>

用途总结:组合模式可以对容器和物体(这里的地图包和地图)统一处理,其他代码处理这些对象的时候,不必过于追究谁是容器,谁是物体。这里为了简化说明,没有深入探讨,其实组合模式常常用于和迭代模式结合,比如我们可以用统一的方法(就像这里的showChildren方法),获取地图包下所有的地图名(包括子目录)

实现总结:用一个基类实现一些容器和物体共用的方法,比如上面的abstractMap,然后让容器和物体类继承基类。由于各自的特性不同,在容器和物体类中重载相应的方法,比如addChild方法。这样对外就可以用统一的方法操作这两种对象。