-
Notifications
You must be signed in to change notification settings - Fork 27
/
6.html
394 lines (391 loc) · 42.6 KB
/
6.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<title>第六章 简化使用 Session 和安全</title>
<link href="css/main.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/main.js"></script>
</head>
<body>
<div id="container"><a name="top"></a>
<div id="header">
<div class="title"><a href="5.html">上一页</a> | <a href="7.html">下一页</a> | <a href="table_of_contents.html">目录</a> | <a href="#bottom">转到页尾</a></div>
</div>
<div id="content">
<div class="main">
<h1>第六章 简化使用 Session 和安全</h1>
<p>理论说得够多了!现在让我们开始写我们自己的应用。在这一章里,我们将会大致描述一下我们要建立的一个应用系统,而且我们分析一些会影响到网站系统的基本问题也就是 Session 管理和安全。</p>
<p>在这一章,我们将会看到:</p>
<ul>
<li>如何使你的网页安全</li>
<li>如何使用 CI 的 Session 类</li>
</ul>
<a name="C_6_1"></a>
<h2>6.1 开始用 CI 设计一个实际的网站</h2>
<p>我们已经看过了 CI 的 welcome 页面,并且了解了它是怎样通过控制器和视图文件创建的。实际上这就是CI的“hello world”。</p>
<p>从前,业余人士写的发烧友网站都使用开源系统,而且总是被一些所谓的大公司看成是低档的玩艺儿。</p>
<p>不过现在,套用一句老话,时代不同了。很多大公司开始使用开放源代码技术。举例来说,NASA(美国太空总署)和美联社使用MySQL、US GAO和Yahoo正在使用PHP的应用。而且我相信规模适中,具有高度灵活性的动态网站正在迅速增长。当大公司了解到那些他们一直使用的商业技术无法满足新的需求后,他们引进规模适中、具有高度灵活性的系统代替原来的系统。</p>
<p>CI无疑是设计开发规模适度、具有高度灵活性的网站的利器。它手把手地教你规划网站,并做出一致而可靠的应用。</p>
<p>为了示范CI的灵活性,让我们开始创建一个应用。</p>
<p>重新说一下我的需求,我要让这个网站为我解决问题。我正在维护着一些网站,有些是我自己的,有些是我的客户的。我需要对这些网站进行日常维护,测试它们,确保它们运转良好。大部分的工作内容是重复性的,我可以雇人来做这件事,但是写一个网站将会是比较合理的方案,因为它能自动化的完成工作,不管白天还是黑夜。</p>
<p>因此我的需求是:</p>
<ol>
<li>不需要人工干预,管理一个或更多的远程网站</li>
<li>定期检测远程网站</li>
<li>生成报表, 提供网站的细节和测试结果</li>
</ol>
<p>如果ISP允许的话,我想设定网站能运行Cron(注:Linux下的计划任务软件),如果不允许,我会自己一天两次或者1小时1次地运行,让它运行一个预设的测试方案。</p>
<p>我不想知道细节,除非什么东西出现故障(然后我想要让它发送一封Email告诉我出了什么事,是哪个环节出的事),然后我希望能打印出管理报告,让我的客户意识到我一直在做定期的检查,并正常地运行他们的网站。</p>
<p class="important">注意:为了避免使代码太长和过于重复,因此在这本书里的代码并不足够安全,因此请记住不要在实际应用环境中使用这些代码。这一章涉及了针对未授权用户的相关的安全知识。但是,确保网站安全的一般概念等其他PHP安全问题,本书没有涉及。</p>
<p>在现阶段,我们可以看到CI的实现方式与一般动态网站的方式相似,因此让我们先放下设计网站这个主题,从一些非常基本的概念谈起。</p>
<a name="C_6_2"></a>
<h2>6.2 关于网站</h2>
<p>任何的网站是一个各类应用的集合,而且它们彼此之间应该能够交互。我们在第三章看到,CI使用URL进行链接。典型的URL如下表:</p>
<table width="95%" border="1" align="center" cellpadding="5" cellspacing="0">
<tr>
<td width="17%" valign="top">基本 URL</td>
<td width="83%">http://www.mysite.com 这是最基本的 URL,用户用它定位你的网站。用户不需要知道 URL 的其余部分,网站会在需要时自动加上其它部分</td>
</tr>
<tr>
<td valign="top">index 文件</td>
<td>第 1 段:index.php<br />
这是 CI 的网站主文件</td>
</tr>
<tr>
<td valign="top">类(或称为控制器)</td>
<td> 第 2 段:start<br />
如果没有设置控制器,CI会从config文件中寻找默认控制器 </td>
</tr>
<tr>
<td valign="top">方法(或称为函数)</td>
<td>第 3 段:assessme<br />
如果没有设定函数,CI会使用本控制器中的默认函数,找不到就重定向到404页</td>
</tr>
<tr>
<td valign="top">附加的任何的参数</td>
<td>第 4 段:fred(第 5 段:12345,第 6 段:Hello,等等。)</td>
</tr>
</table>
<p>因此,在start控制器中调用accessme函数,并传入fred,12345两个参数,你的网址将会是:</p>
<p>http://www.mysite.com/index.php/start/assessme/fred/12345</p>
<p>系统会查找start.php,其中有一个Start类,它的内部有一个accessme方法,需要传入两个参数,现在传给这两个参数的值分别是fred和12345。一个这样的URL可以调用你网站上任何一个控制器中的任何功能。</p>
<p>为了体会它如何工作,让我们建立我们网站的第一个页面。我们将会建立叫做Start的控制器,而且把它设定为默认控制器。</p>
<p>好吧,首先,因为安装CI时,它指定welcome为默认控制器,因此我需要改变这个设置。CI默认的路由存放在下列路径:/system/application/config/routes。当前设置为:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$route</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'default_controller'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">"welcome"</span>;</pre>
</div>
<p>我们把它改成:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$route</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'default_controller'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">"start"</span>;</pre>
</div>
<p>(记住,如果你的默认控制器中没有一个index方法,如果使用基本URL,系统会显示404错误)</p>
<p>现在我需要编写我的start控制器。记住基本的格式:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #000000; font-weight: bold;"><?php</span>
<span style="color: #000000; font-weight: bold;">class</span> Start <span style="color: #000000; font-weight: bold;">extends</span> Controller
<span style="color: #66cc66;">{</span>
<span style="color: #000000; font-weight: bold;">function</span> Start<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span>
parent::<span style="color: #006600;">Controller</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span>
<span style="color: #000000; font-weight: bold;">function</span> assessme<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$name</span>, <span style="color: #0000ff;">$password</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span>
<span style="color: #b1b100;">if</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$name</span> == <span style="color: #ff0000;">'fred'</span> && <span style="color: #0000ff;">$password</span> == <span style="color: #ff0000;">'12345'</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">mainpage</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<span style="color: #66cc66;">}</span>
<span style="color: #66cc66;">}</span>
<span style="color: #66cc66;">}</span>
<span style="color: #000000; font-weight: bold;">?></span></pre>
</div>
<p>把这个文件保存为/system/application/controllers/start.php。</p>
<p>第二行告诉你这是一个控制器。然后构造函数装载父控制器构造方法。assessme 函数需要两个变量 $name 和 $password。CI(从1.5版开始)自动分配URL的相关部分作为参数,因此fred和12345将会变成那$name和$password。</p>
<p>因此,如果我们键入上面的网址,如果用户存在,密码无误,会被重定向到mainpage()函数。我们会在start控制器中增加这个函数。(如果用户检测不能通过,系统运行结束。)</p>
<p>对于那些过去一直使用过程式编程而不是OO编程的人来说,请注意一个类中的方法必须被表示成$this->xxxx。因此,如果我调用start控制器里的mainpage()函数,我必须这样表示$this->mainpage().否则,CI会找不到它。</p>
<p>当然, 一般用户不会这样定位一个网站:</p>
<p>http://www.mysite.com/index.php/start/assessme/fred/12345</p>
<p>他们仅仅输入:</p>
<p>http://www.mysite.com</p>
<p>而且希望网站给出导航条。因此让我们让这里开始。</p>
<p>通常,你在网站上看到的第一个页面是登录页面。因此让我们来做一个。首先,我把一个新的函数加入start控制器。我想要网站默认调用这个函数,因此,我将它命名为index()</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #000000; font-weight: bold;">function</span> index<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$data</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'mytitle'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">"My site"</span>;
<span style="color: #0000ff;">$data</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'base'</span><span style="color: #66cc66;">]</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">config</span>-><span style="color: #006600;">item</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'base_url'</span><span style="color: #66cc66;">)</span>;
<span style="color: #0000ff;">$data</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'css'</span><span style="color: #66cc66;">]</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">config</span>-><span style="color: #006600;">item</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'css'</span><span style="color: #66cc66;">)</span>;
<span style="color: #0000ff;">$data</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'mytitle'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">"A website to monitor other websites"</span>;
<span style="color: #0000ff;">$data</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'text'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">"Please log in here!"</span>;
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">view</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'entrypage'</span>, <span style="color: #0000ff;">$data</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span></pre>
</div>
<p>它调用视图:entrypage。视图中包含一个表格,这个表格允许用户输入用户名和密码。 HTML表格需要指定一个处理$_POST的函数,我们已经把它放在start控制器中,就是 assessme()。用HTML代码表示,在我们的视图上的表格应该是:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">HTML 代码</div>
</div>
<pre class="html4strict"><span style="color: #009900;"><a href="http://december.com/html/4/element/form.html"><span style="color: #000099;"><form</span></a> <span style="color: #000066;">method</span>=<span style="color: #ff0000;">"post"</span> <span style="color: #000066;">action</span>=<span style="color: #ff0000;">"http://www.mysite.com/index.php/start/assessme"</span> /<span style="color: #000099;">></span></span></pre>
</div>
<p>我已经稍稍解释了assessme函数,它接受一个用户/密码组合,我需要在一个数据库中查询它。为了要使代码更结构化,我们要引入一个函数叫做checkme()。</p>
<p>所以,你会见到assessme()调用checkme(),Checkme()在数据库里检查用户是否存在,密码是否有效,然后把'是'或'不'返回给assessme()。如果返回是,assessme()调用另外一个函数,mainpage(),返回一个视图。</p>
<p>注意代码模块的好处。每个函数扮演一个角色。如果我需要改变系统检查密码的方式,我只需要改变checkme()函数。如果我需要修改页面,我只要修改mainpage()函数。</p>
<p>让我们看看代码结构以及各部分如何交互。注意,为了要使例子简洁,我们不校验用户的输入。当然,这不会造成问题,CI的表格类自动为输入数据“消毒”。</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #808080; font-style: italic;">/*receives the username and password fromthe POST array*/</span>
<span style="color: #000000; font-weight: bold;">function</span> assessme<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$username</span> = <span style="color: #0000ff;">$_POST</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'username'</span><span style="color: #66cc66;">]</span>;
<span style="color: #0000ff;">$password</span> = <span style="color: #0000ff;">$_POST</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'password'</span><span style="color: #66cc66;">]</span>;
<span style="color: #808080; font-style: italic;">/*calls the checkme function to see if the inputs are OK*/</span>
<span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">checkme</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$username</span>,<span style="color: #0000ff;">$password</span><span style="color: #66cc66;">)</span>==<span style="color: #ff0000;">'yes'</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #808080; font-style: italic;">/*if the inputs are OK, calls the mainpage function */</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">mainpage</span>;<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">}</span>
<span style="color: #808080; font-style: italic;">/*if they are not OK, goes back to the index function, which re-presents the log-in screen */</span>
<span style="color: #b1b100;">else</span><span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">index</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span>
<span style="color: #66cc66;">}</span>
<span style="color: #808080; font-style: italic;">/*called with a u/n and p/w, this checks them against some list. For the moment, there's just one option. Returns 'yes' or 'no' */</span>
<span style="color: #000000; font-weight: bold;">function</span> checkme<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$username</span>=<span style="color: #ff0000;">''</span>, <span style="color: #0000ff;">$password</span>=<span style="color: #ff0000;">''</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$username</span> == <span style="color: #ff0000;">'fred'</span> && <span style="color: #0000ff;">$password</span> ==<span style="color: #ff0000;">'12345'</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #b1b100;">return</span> <span style="color: #ff0000;">'yes'</span>;
<span style="color: #66cc66;">}</span> <span style="color: #b1b100;">else</span> <span style="color: #66cc66;">{</span>
<span style="color: #b1b100;">return</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'no'</span>;
<span style="color: #66cc66;">}</span>
<span style="color: #66cc66;">}</span></pre>
</div>
<p>在第5-6行上,assessme()接收来自表格的$_POST数组,包含以下一些数据:</p>
<p>[username] => fred</p>
<p>[password] => 12345</p>
<p>assessme() 函数把这两个变量传递给另一个函数 checkme()。该函数检测具有密码12345的用户fred是否是合法的用户,如果他们是,它返回'yes'。显然地,在一个真正的网站上,这会更复杂。你或许会为有效的用户名/密码做数据库查询。把它定义成一个单独的函数意味着我可以测试我的代码的其它部分,并在稍后改进checkme()函数。</p>
<p>如果使用者名称和密码是一个有效的组合,assessme()函数调用另一个函数mainpage(),让用户进入网站。否则,它返回到index()函数-即,再次显示登录窗口。</p>
<p>下一个问题是如何处理状态。换句话说,当进入到另一页面时如何识别这是一个已登录的用户。</p>
<a name="C_6_3"></a>
<h2>6.3 安全/Session:使用另一个 CI 类库</h2>
<p>如果我想要建立一个会话机制确保未授权用户无法存取我的文件,需要多少代码?</p>
<p>英特网通过一系列请求工作。你的浏览器给我的服务器发请求,要求某一特定页。我的浏览器把该页处理后返回你的服务器。服务器处理后向浏览器发出另一页面,你再按下一个超链接,对我的服务器提出新的页面请求。如此而已。</p>
<p>英特网是'没有状态的'——就是,你的浏览器对我的服务器做的每个请求都被当做一个单独事件,HTTP协议没有办法把你的请求和任何其他的请求联系起来。</p>
<p>如果你想要你的网站页面与页面之间建立联系,你必须管理“状态”,要服务器记得哪些请求来自你的浏览器, 需要做一些特别的处理。</p>
<p>PHP提供管理状态的两种办法:使用cookie或使用session会话。PHP的session自动地检查你的浏览器是否接受cookie。如果不接受,它会使用会话ID的方法,这个ID直接通过URL进行传递。</p>
<p class="important">Cookie是小块的数据由服务器发送到你的浏览器。浏览器自动地保存它。一旦保存了cookie,当浏览器尝试访问网站的时候,网站就会检查是否存在cookie。如果它找到正确的cookie,它能读取它里面的数据适当地配置它本身。可能关闭特定页面,或显示你的个人数据。</p>
<p>因为一些人设定他们的浏览器不接受cookie,PHP提供其它变通的方法。每次当浏览器请求访问时,网站产生'会话ID',把它发送到浏览器端,当浏览器发送下个请求时把这个ID加入URL,所以网站能够识别浏览器。</p>
<p>CI 有一个 Session 类来处理会话相关工作。事实上,它大大减少了编码量。我们在上一章学习到CI有各种各样的library,大大简化了PHP的编程。这些就是框架的核心:集成大量代码,为你提供各种强大的功能。你要做的只是使用它们,而不必关心他们是如何运作的。作为结果,你的应用使用了最专门的代码,而你却不需要自己去编写它们!</p>
<p>如果你想要在你的控制器内或模型中使用某个类内的函数,你一定记得首先要装载这个类,(有些类,比如config总是自动地被装载,这是为什么我们不需要在代码中装载它的原因。)</p>
<p>你可以这样很简单地装载类库:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">library</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'newclass'</span><span style="color: #66cc66;">)</span>;</pre>
</div>
<p>通常,把这一行放入你的控制器或模型的构造函数中。</p>
<p>如果你认为你会在每个控制器中使用某个library,你能像config类一样自动地装载它。找到/system/application/config/autoload文件,增加你想自动装载的类名:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$autoload</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'libraries'</span><span style="color: #66cc66;">]</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;</pre>
</div>
<p>因此它看起来像这一样:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$autoload</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'libraries'</span><span style="color: #66cc66;">]</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'newclass'</span>, <span style="color: #ff0000;">'oldclass'</span><span style="color: #66cc66;">)</span>;</pre>
</div>
<p>在这里,我们首先要用到session类,这帮助你维持状态,而且可以识别用户。做到这一点相当简单。下面列出增强的assessme()函数:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #000000; font-weight: bold;">function</span> assessme<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">library</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'session'</span><span style="color: #66cc66;">)</span>;
<span style="color: #0000ff;">$username</span> = <span style="color: #0000ff;">$_POST</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'username'</span><span style="color: #66cc66;">]</span>;
<span style="color: #0000ff;">$password</span> = <span style="color: #0000ff;">$_POST</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'password'</span><span style="color: #66cc66;">]</span>;
<span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">checkme</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$username</span>, <span style="color: #0000ff;">$password</span><span style="color: #66cc66;">)</span> == <span style="color: #ff0000;">'yes'</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">mainpage</span>;<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">}</span> <span style="color: #b1b100;">else</span> <span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">index</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span>
<span style="color: #66cc66;">}</span></pre>
</div>
<p>(你可以看到,在函数一开始就装载session library,但是通常,我们将会在控制器的构造函数中装载它,因此,在这一个类中的其它函数都可以使用这个类。)</p>
<p>当这个类装入后,立即通过它本身强大的功能为你提供对会话的读取,创建,修改等功能。</p>
<p>好吧,坦白地讲,不仅仅就一行代码那么简单。你必须首先修改config文件,告诉 Session 类你想要它做的。</p>
<p>检查你的system/application/config/config.php 文件,你会找到像这样的一个部分:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #808080; font-style: italic;">/*--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'session_cookie_name' = the name you want for the cookie
| 'encrypt_sess_cookie' = TRUE/FALSE (boolean). Whether to encrypt the cookie
| 'session_expiration' = the number of SECONDS you want the session to last.
| by default sessions last 7200 seconds (two hours). Set to zero for no expiration.
|
*/</span>
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_cookie_name'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">'ci_session'</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_expiration'</span><span style="color: #66cc66;">]</span> = <span style="color: #cc66cc;">7200</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_encrypt_cookie'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">FALSE</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_use_database'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">FALSE</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_table_name'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">'ci_sessions'</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_match_ip'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">FALSE</span>;
<span style="color: #0000ff;">$config</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'sess_match_useragent'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">FALSE</span>;</pre>
</div>
<p>现在,确定[sess_use_database]被设定成FALSE。</p>
<p>保存设置,现在,每次当你的客户登入网站,网站会在你的机器上保存一个cookie,包含下列的数据:</p>
<ul>
<li>CI生成的一个唯一的SESSION ID,这是CI为这个会话生成的任意组合的字符串。</li>
<li>用户的IP地址</li>
<li>用户的 User-Agent 数据(浏览器数据的最初 50 个字符)</li>
<li>最大有效时间</li>
</ul>
<p>如果你设定sess_encrypt_cookie为FALSE,你能在你的浏览器上看到cookie的内容:(它部分被编码了,但是你能辨认出来):</p>
<p>ip_address%22%3Bs%39%3%22127.0.0.1%22%3Bs%310%3A22</p>
<p>包括使用者的URL,本例为127.0.0.1。如果你设定sess_encrypt_cookie为TRUE,cookie被加密,只是一行无意义的代码。浏览器不能识别它,当然使用者也无法修改它。</p>
<p>当使用者发出另一个页面请求时,网站会检查会话ID是否已经以cookie的形式保存在用户的机器里,如果有,这会作为一个已经存在的会话的一部分,如果没有,就会建立一个新的会话。记得在所有的其它控制器上加载 Session 类。CI将作同样的检查。我必须做的只是告诉每个控制器当没有cookie时该如何运作。</p>
<a name="C_6_3_1"></a>
<h2>6.3.1 使 Session 更安全</h2>
<p>这本来不会造成安全问题。任何访问网站的用户都会开始一个会话。代码只是判断他们是否是一个新用户。避免未认证的用户访问某些页面的一个方法是如果他们已经登录,就在cookie中加入一些信息,因此我们可以对此进行验证。一旦用户输入正确的用户名和密码一次,就会在cookie中被记录,当发出新的请求時,会话机制会检查cookie,如果是已登录的用户,就返回用户请求的页面,如是不是,就返回登录画面。</p>
<p>把信息加入cookie很容易。在我的assessme()控制器中,如果密码和使用者名称验证通过,加上如下代码:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #b1b100;">if</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">checkme</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$username</span>, <span style="color: #0000ff;">$password</span><span style="color: #66cc66;">)</span>==<span style="color: #ff0000;">'yes'</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
<span style="color: #0000ff;">$newdata</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span>
<span style="color: #ff0000;">'status'</span> => <span style="color: #ff0000;">'OK'</span>,
<span style="color: #66cc66;">)</span>;
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">session</span>-><span style="color: #006600;">set_userdata</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$newdata</span><span style="color: #66cc66;">)</span>;
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">mainpage</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span></pre>
</div>
<p>这样会把$newdata数组中内容-上例中只有一个变量-加入到cookie中。现在,每当密码/用户名组合是可接受的,assessme()将会把'OK'加入cookie,而且我们可以把这些代码加到每个控制器中:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #808080; font-style: italic;">/*remember to load the library!*/</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">library</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'session'</span><span style="color: #66cc66;">)</span>;
<span style="color: #808080; font-style: italic;">/*look for a 'status' variable in the contents of the session cookie*/</span>
<span style="color: #0000ff;">$status</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">session</span>-><span style="color: #006600;">userdata</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'status'</span><span style="color: #66cc66;">)</span>;
<span style="color: #808080; font-style: italic;">/*if it's not there, make the user log in*/</span>
<span style="color: #b1b100;">if</span><span style="color: #66cc66;"> (</span>!<a href="http://www.php.net/isset"><span style="color: #000066;">isset</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$status</span><span style="color: #66cc66;">)</span> || <span style="color: #0000ff;">$status</span> != <span style="color: #ff0000;">'OK'</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span> <span style="color: #808080; font-style: italic;">/*function to present a login page again...*/</span><span style="color: #66cc66;">}</span>
<span style="color: #808080; font-style: italic;">/*otherwise, go ahead with the code*/</span></pre>
</div>
<p>这样,你在你的网站周围构建起安全的地基。你可以很容易地使它变得更精细。举例来说,如果你的部分用户有更高级的权限,你可以在cookie中保存了个存取级别码,而不是只放进一个“OK”,你能用这个进行更细致的权限调控。</p>
<p>在一个cookie中存放明码数据会比较糟糕,因为用户可以打开cookie并看到它的内容,当然也可以很容易地修改它。因此使用CI加密cookie会是一个很好的办法。还有,你可以在数据库中建立一个用户表,在用户登录以后修改用户表的登录状态,在用户表中维护一些权限属性,每次访问时,从数据库的用户表中读取权限信息,也是一个比较好的做法,如果结合缓存技术,就会解决系统的性能问题。</p>
<p>在你的数据库中保存会话数据非常简单。首先,创建数据库表。如果你正在使用MySQL, 使用SQL语句:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">MySQL 代码</div>
</div>
<pre class="mysql"><span style="color: #993333; font-weight: bold;">CREATE TABLE</span> <span style="color: #993333; font-weight: bold;">IF</span> <span style="color: #aa3399; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">EXISTS</span> `ci_sessions` <span style="color: #66cc66;">(</span>
session_id <span style="color: #aa9933; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">(</span><span style="color: #cc66cc;">40</span><span style="color: #66cc66;">)</span> <span style="color: #aa3399; font-weight: bold;">DEFAULT</span> <span style="color: #ff0000;">'0'</span> <span style="color: #aa3399; font-weight: bold;">NOT NULL</span>,
ip_address <span style="color: #aa9933; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">(</span><span style="color: #cc66cc;">16</span><span style="color: #66cc66;">)</span> <span style="color: #aa3399; font-weight: bold;">DEFAULT</span> <span style="color: #ff0000;">'0'</span> <span style="color: #aa3399; font-weight: bold;">NOT NULL</span>,
user_agent <span style="color: #aa9933; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">(</span><span style="color: #cc66cc;">50</span><span style="color: #66cc66;">)</span> <span style="color: #aa3399; font-weight: bold;">NOT NULL</span>,
last_activity <span style="color: #aa9933; font-weight: bold;">INT</span><span style="color: #66cc66;">(</span><span style="color: #cc66cc;">10</span><span style="color: #66cc66;">)</span> <span style="color: #aa3399; font-weight: bold;">UNSIGNED</span> <span style="color: #aa3399; font-weight: bold;">DEFAULT</span> <span style="color: #cc66cc;">0</span> <span style="color: #aa3399; font-weight: bold;">NOT NULL</span>,
STATUS <span style="color: #aa9933; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">(</span><span style="color: #cc66cc;">5</span><span style="color: #66cc66;">)</span> <span style="color: #aa3399; font-weight: bold;">DEFAULT</span> <span style="color: #ff0000;">'no'</span>,
<span style="color: #993333; font-weight: bold;">PRIMARY KEY</span> <span style="color: #66cc66;">(</span>session_id<span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">)</span>;</pre>
</div>
<p>然后,修改在system/application/config/database.php文件中的连接参数告诉CI数据库在哪里。详细介绍参见第四章</p>
<p>如果工作正常的话,当你登录或退出时,在你的数据库表中增加了相关的数据。如果你在一张数据库表中储存会话,当用户登录到你的网站,网站会查找cookie,如果它找到了,就会读取会话ID,并用这个ID去匹配数据库表中保存的ID。</p>
<p>你现在拥有一个强健的会话机制。而这一切只需要一行代码!</p>
<p>一点声明,PHP本身的 Session 类在用户关闭cookie功能时能够应付。(取代生成cookie,它把会话数据加入URL) CI类不会这样做,如果使用CI,一旦用户禁止cookie,他就不能登录到你的网站。这方面需要增强功能。希望Rich将会很快升级CI。</p>
<a name="C_6_4"></a>
<h2>6.4 安全</h2>
<p>注意到:Session 类会自动地保存关于访问者的IP地址和使用者的资料。你可以使用一些增强的安全措施。</p>
<p>通过修改config文件的两处设置可增加安全性:</p>
<ul>
<li>sess_match_ip:如果你设定这个参数为TRUE,当它读取会话数据时,CI将会尝试相配使用者的IP地址。这将预防使用者使用'抢'来的cookie信息。但是,有些服务器(ISP和大公司服务器)可能存在不同IP地址上的相同使用者登录的请求。如果你设定这个参数为TRUE,可能会给他们造成麻烦。</li>
<li>sess_match_useragent:如果你设定这为TRUE,当读取会话数据的时候,CI将会试着匹配使用者代理。这意谓试着“抢夺”会话的人还需要匹配真正用户的user_agent设置。它使“抢夺”变得稍稍有些困难。</li>
</ul>
<p>CI 也有一个user_agent类,你可以这样装载:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">library</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'user_agent'</span><span style="color: #66cc66;">)</span>;</pre>
</div>
<p>一旦装载,你能要求它返回访问你网站的浏览器和操作系统的各种信息,而不管它是一个浏览器,手机或机器人。比如,如果你想列出参观你的网站的机器人,你可以这样做:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #0000ff;">$fred</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">agent</span>-><span style="color: #006600;">is_robot</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$fred</span> == <span style="color: #000000; font-weight: bold;">TRUE</span><span style="color: #66cc66;">)</span>
<span style="color: #66cc66;">{</span><span style="color: #0000ff;">$agent</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">agent</span>-><span style="color: #006600;">agent_string</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
<span style="color: #808080; font-style: italic;">/*add code here to store or analyse the name the user agent is returning*/</span>
<span style="color: #66cc66;">}</span></pre>
</div>
<p>类通过装入后开始工作,与用代理、浏览器、机器人和其它的类型的数组作比较,这个数组存放在system/application/config/user_agents。</p>
<p>如果你愿意,你可以容易开发出使你的网站锁定特定机器人,特定浏览器类型的功能。不过,记得一个攻击者很容易写出一个代理类型,并返回他想要扔给你的那种类型。因此,他们能容易地伪装成通常的浏览器。许多机器人,包括像CI的user_agents数组已列出的 Googlebot,是'品行端正的'。这意味着如果你设定你的robots.txt文件排除他们,他们将不会强行攻击。没有容易的方法去阻击一个不知名的的机械手,除非你预先知道他们的名字!</p>
<p>在 CI框架中,会话机制保存那发出请求的IP地址,因此你可以使用这个功能维护一个网站的黑名单。可以用如下代码取回来自 Session 的 IP:</p>
<div class="code">
<div class="title">
<div style="float:right"><img class="copyCodeImage" src="images/copycode.gif" alt="" /><a href="javascript:void(0)" onclick="CopyCode(this)">复制代码到剪贴板</a></div>
<div style="clear:none">PHP 代码</div>
</div>
<pre class="php"><span style="color: #808080; font-style: italic;">/*remember to load the library!*/</span>
<span style="color: #0000ff;">$this</span>-><span style="color: #006600;">load</span>-><span style="color: #006600;">library</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'session'</span><span style="color: #66cc66;">)</span>;
<span style="color: #808080; font-style: italic;">/*look for an 'ip_address' variable in the contents of the session cookie*/</span>
<span style="color: #0000ff;">$ip</span> = <span style="color: #0000ff;">$this</span>-><span style="color: #006600;">session</span>-><span style="color: #006600;">userdata</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'ip_address'</span><span style="color: #66cc66;">)</span>;</pre>
</div>
<p>因此你能针对黑名单作相应的安全处理,比如拒绝登录。</p>
<p>你也可以用CI的会话机制限制来自重复请求的损害-像是一个机器人通过重复地请求网页来使你的网站超载。你也可以使用这一个机制处理'字典'攻击,即一个机器人不断重复尝试数百或者数以千计的密码/用户名组合直到它找到正确的一组。</p>
<p>因为 CI 的 Session 类保存每个会话的last_activity,所以你能做到这一点。每次,当网页被请求时,你能检查多久以前这个IP地址的用户发出一请求,两次请求的间隔如果不正常,你可以终止会话,或放慢响应的速度。</p>
<a name="C_6_5"></a>
<h2>6.5 总结</h2>
<p>我们已经概略介绍了我们想要建立的一个应用,和几乎所有应用都需要解决的问题:Session 管理和安全。</p>
<p>为了做到这一点,我们已经学习了 CI Session 类库的一些细节,而且见到了它如何生成 Session 记录和在访客的浏览器中生成 Cookie。</p>
<p>当后续的请求发生时,你可以读取 Cookie 对响应进行控制。</p>
</div>
</div>
<a name="bottom"></a>
<div id="footer">
<div class="title"><a href="5.html">上一页</a> | <a href="7.html">下一页</a> | <a href="table_of_contents.html">目录</a> | <a href="#top">转到页首</a></div>
<div style="clear:none">第六章 简化使用 Session 和安全</div>
</div>
</div>
</body>
</html>