4.可选择的服务器框架
下面介绍几种java Servlet的可替代方案
4.1 服务端脚本
使用Java或者C来编写一个web应用,即使是很简单的应用程序也是很费时间的,一种可替代方案是服务端脚本(server-side scripting),脚本语言提供了可被嵌入HTML的结构。
在服务器的脚本中,服务器在传递一个web页面之前会执行嵌入在html内容中的脚本,脚本在执行时可以生成加入该页面的文本(或者甚至可能从该页面删除内容)。脚本的源码将从该页面被删除,因此客户端可能根本没有察觉到该页面中原先是含有代码的。被执行的脚本也可能包含在数据库上执行的SQL代码,许多这样的语言都带有库和工具,它们共同构成了用于web应用程序开发的框架。
广泛应用的脚本框架有JSP,ASP.NET,PHP以及Ruby on Rails。
4.1.1 JSP
JSP全称是Java服务器页面(Java Server Pages)。它允许将Java嵌入HTML页面,从而实现将静态的HTML页面与动态的HTML页面的混合在一起。这样的好处在于:大量的动态Web页面,其大多数内容仍然是静态的(也就是说,不论页面何时生成,总是显示相同的内容)。通过编写servlet来创建这样的页面,会导致大量的HTML代码被编码成为第Java字符串。JSP允许将这小部分的Java代码被嵌入静态的HTML中,被嵌入的Java代码生成该页面的动态部分,JSP脚本实际上被转换成为servlet代码然后进行编译,但是程序员从撰写大量的Java代码以创建servlet的困境中解脱出来了。
下图表示一个JSP页面,其中用<%...%>括起来的部分为Java代码。
JSP也支持标签库,它允许使用这样的标签:它们看起来非常像HTML标签,但是却在服务段解释,并用对应生成的HTML来取代的。如有需要,可以查阅相关文档。
4.1.2 PHP
PHP是一种广泛应用与服务 器脚本的脚本语言,类似与JSP,它可以与HTML混用,并且它有许多可用的库,包括使用ODBC访问数据库的库。下面是一个示例。
4.2 Web应用框架
web应用框架会在多个方面来简化web应用的开发,有很多这种框架,比如Python语言的Django框架,Ruby语言的Ruby on Rails,Apache Structs,Swing,spring mvc。下图是一个spring mvc的工作流程,可以看到它帮我们做了很多工作,简化了程序员的开发。
5.客户端代码和web服务
5.1 javascript
早期的web浏览器只显示html代码,但人们很快就需要客户端的web浏览器与用户做更加灵活的交互,因此客户端脚本语言开始被广泛应用。
Javascript式目前最广泛的客户端脚本语言。
5.1.1 输入验证
可以用Javascript来执行用户输入的错误检查(验证)
HTML5已经支持许多验证,但是复杂的验证还需要JavaScript,下面就是一个示例。
5.1.2 响应式用户界面
JavaScript的一个重要作用就是可以在浏览器创建高度响应式用户界面。创建一个这样的界面最关键的是能够动态地修改通过JavaScript来显示的HTML代码。浏览器将HTML代码解析为一个内存中的树结构,该树结构是由文本对象模型(Document Object Model,DOM)的标准来定义的。JavaScript能够修改这个树结构以执行特定的操作。例如一个表单,通过一个按钮触发“添加项目”,就可以增加表单的行。
虽然JavaScript语言已经被标准化,但是在浏览器之间还是存在差别,为了避免在一个浏览器上能够工作的JavaScript在另外一个浏览器上不工作,最好提供一个JavaScript库,比如JQuery库。它允许以一种独立于浏览器的方式来编写代码。库里的函数能够在内部找出正在使用的是哪种浏览器,并向该浏览器发送对应生成的JavaScript。
诸如JQuery那样的JavaScript库提供了许多UI元素,比如菜单、选项卡、滑块。
HTML5标准支持多种丰富的用户交互功能,包括拖放、地理位置、事件(它允许后端发生某些事件时通知前端)等。
5.1.3 与web服务的接口
如今,JavaScript也被广泛的用于创建动态网页,它使用统称为Ajax的几种技术。用JavaScript编写的程序可以和Web服务器异步通信(在后台不阻塞用户的方式与Web浏览器交互)。JavaScript对象表示法(JavaScript Object Notation,JSON)是最广泛的用于数据传输的数据格式,尽管也使用诸如XML那样的格式。
下面举一个Ajax的使用实例。下面代码调用JQuery库实现了很常见的代码自动补全技术。
可以看到上面调用了JQuery的autocomplete函数将用户键入的name进行补全,访问的后端接口是/autocomplete_name。在html的<body>中,定义了一个button按钮show details,点击后就会触发函数loadTableAsync(),这个函数受限会创建一个URL字符串url,该字符串会调用/person_query_ajax,使用ajax.url.load()函数从web服务中获取JSON数据来填充表的行。
这是异步发生的,也就是说,函数会立刻返回,当获取到数据时,表中的行被填充为返回的数据。使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。
它的功能效果如下。
5.2 web服务
Web服务(Web service)是一个应用组件,它可以通过Web和函数来调用,在效果上类似于应用编程接口。Web服务请求是使用HTTP协议来发送的,它在应用服务器上执行,并且结果会被发送会调用函数。
有两种方式被广泛应用于Web服务。第一种是较为简单的REST(Representation State Transfer,表示状态转移),第二种是更加复杂且使用频率更低的方式,被称为“大Web服务”。
RESTful风格的服务对URL的标准HTTP请求来执行Web服务的调用,其参数作为标准HTTP请求的参数来发送,应用服务器执行该请求,生成结果并对结果编码,将结果作为HTTP请求的结果来返回。当前最广泛的编码方式是JSON,其次是XML。
除了前后端的交互,Web 服务也被越来越多的用于后端,以利用其他后端系统所提供的功能。比如我们可以同Web服务的API来调用一个第三方的文本转语音的服务。
您可以参考这篇博客,获得对web service的更深刻理解。
Web Service是什么
“大Web”服务对参数和结果使用XML编码,使用一种专门的规范来定义Web API,并使用在HTTP协议之上构建的一个协议层。
5.3 断连操作
很多应用希望即使客户端与服务段断开,仍然支持某些操作。例如,一个学生填写一个申请表单,但是他没有提交的情况下断网了,他希望网络恢复时填写的表单内容还能恢复。构建这种应用需要客户端机器中的本地存储。
if(typeof(Storage) !== "undefined") { // 浏览器支持本地存储 ... }
不是所有的浏览器都支持本地存储,如果支持,您可以通过下面的函数来存储、加载或者删除内容到本地。
浏览器可以限制一个网站最多存储的数据量,缺省情况是5MB。
HTML5支持IndexedDB,允许存储多个属性上具有索引的JSON对象,还支持数据的模式版本。
5.4 移动应用平台
移动应用(mobile APP)被设计来针对手机等移动设备来使用,它具有很多针对移动设备的特定优势,比如更适应小屏幕,更完善的授权模型,调用位置、摄像头、联系人等;在高速网络下载,在低速网络使用等。
但是移动应用的代码不能够通用,比如一个移动应用需要为了安卓和IOS开发不同的代码。
Web APP综合了web应用和移动应用的优点,可以解决需要编写两套代码的烦恼,在逐渐代替一部分移动应用。
6.应用程序体系结构
为了处理大型应用程序的复杂性,通常将他们分层,一种分层方式是分为三层:展示层或用户界面层,业务逻辑层,数据访问层。下图展示了一个Web应用程序的体系结构。
6.1 业务逻辑层
一个用于管理大学的应用程序的业务逻辑层可能会提供实体的抽象(比如学生、教师、课程),以及操作的抽象(比如录取学生、课程注册)。
另外,业务逻辑包含工作流(workflow),它描述如何处理一个涉及多个参与者的特定任务。比如假单审批需要多个步骤,不同权限的领导进行不同环节的审批。另外,工作流还包括异常情况的处理。
6.2 数据访问层和对象——关系映射
在最简单的场景中,业务逻辑层使用与数据库相同的数据模型,此时数据访问层的作用就是隐藏与数据库接口的细节。然而,当使用面向对象程序设计语言编写业务-逻辑层时,会很自然的将数据建模为对象,并具有在对象上调用的方法。
早期系统中开发人员需要将数据库获取的数据转为对象,将更新后的对象存回数据库而编码,这种手动转换数据模型的方式麻烦而且容易出错。后面人们提出了面向对象数据库,但并没有取得商业上的成功。
一种可用的处理方式是,仍然用关系型数据库,但是建立对象-关系映射(Object-Relational Mapping,ORM)
Hibernate ORM,mybatis ORM是广泛应用于java对象到关系映射的框架, Django ORM适用于Python语言,这里不做展开。
7.应用程序性能
web站点可能一秒需要数千次的速率被人访问,提升其性能很重要,高速缓存可以加快单个请求的处理速度,还可以使用多个应用服务器并行处理多个请求。
7.1 高速缓存
如果每个用户请求到需要通过JDBC连接到一个数据库,频繁的创建连接关闭连接的时间损耗在高并发场景不可接受。
连接池被用来减少这种开销。它有点像共享单车,如果用户请求需要连接,没有未使用的连接则打开一个新的连接,有未使用的连接则使用未使用的连接,用完后将连接归还给连接池,如果有很多打开但长时间没有被使用的连接,连接池会回收一部分连接。
许多应用服务器与较新的JDBC、ODBC驱动都内置了连接池。
大多数时候创建JDBC连接需要将JDBC连接的详细参数(计算机、端口、数据库、用户标识、密码等)提供给创建一个DataSource对象,通过getConnection()获取一个连接。
某些请求会导致向数据库重复提交完全相同的查询,使用高速缓存将这些结果保存可以大大减少数据通信的代价。通过高速缓存为响应一个请求而发送的最终Web页面可以进一步减少开销。
高速缓存的查询结果与高速缓存的web页面都是物化视图的形式,如果底层数据库发生了变动,高速缓存必须被废弃,或者重新计算,甚至增量更新。某些数据库系统(如SQL Server)提供了一种通知(notification)机制确保缓存数据最新。
存在几种广泛应用的主存高速缓存系统,其中比较流行的是memcached和Redis。
7.2 并行处理
处理非常重的负载的一种常用方法是采用大量并行方式运行的应用服务器,每台应用服务器处理一小部分请求。一台Web服务器或者一台网络路由器可以被用于将来自每个客户端请求路由到其中一台应用服务器。来自一个特定客户端会话的所有请求必须被送到同一台应用服务器,因为服务器要维护客户端会话的状态。
除了对应用服务器并行,为了避免数据库过载,还可以使用并行数据库系统,这种系统在需要扩展非常大量的用户的应用程序中很流行。
8.应用程序安全性
8.2 SQL注入
该专栏上一篇文章【数据库05】玩转SQL的高阶特性详细介绍了SQL注入。
假如一个Java程序SQL如下。
"select * from instructor where name = '" + name + "'"
如果用户输入的参数name不是姓名,而是:
X' or 'Y' = 'Y
那么执行的SQL会变成:
select * from instructor where name = 'X' or 'Y' = 'Y'
本来用户只可以按姓名查找数据,现在他窃取了整个关系的数据!!!还有很多诡计多端的注入手段,窃取篡改数据。
使用预备语句可以避免这样的问题。
另外一个可以进行SQL注入的风险来源是基于表单中指定的选择条件和排序属性来动态创建查询的应用程序。例如:
String query = "select * from takes order by " + orderAttribute;
为了避免这种类型的SQL注入,需要在拼接orderAttribute前确保他是我们允许的值。
8.2 跨站点脚本和请求伪造
一个允许用户输入诸如评论或姓名,然后将其保存并在以后显示给其它用户的网站,很容易受到一种叫做跨站点脚本(Cross-Site Scripting, XSS)的攻击。恶意用户可能不输入评论,而输入一段诸如JavaScript或Flash那样的客户端脚本,当其它用户阅览所输入的文本时,浏览器就会执行脚本,恶意窃取数据或者进行操作。
如果不加以防范,恶意用户窃取浏览器的cookie,即使是下面这一行代码就可能导致这特定问题的发生。
<img src="http://mybank.com/transfermoney?amount=1000&toaccount=14523">
这种漏洞又被称为跨站点请求伪造(Cross-Site Request Forgery)或XSRF(有时也被称作CSRF)
为了防止XSRF,要完成两件事。
防止你的网站被用来发动XSS或XSRF攻击。
最简单的方法就是禁止用户输入的任何文本中有任何HTML标签。存在检测或者除去这类标签的函数。在有些情况下需要输入HTML标签,就必须小心设计函数,避免那些伪装得很好的结构。
防止你的网站被从其他站点发动的XSS或XSRF攻击。
HTTP协议允许服务器检查一个页面的引用页,例如检查一个超链接URL是否属于同一个网页的URL。
除了使用Cookie表示会话,还可以将会话限制在原始的IP地址上。
绝对不要用GET方法来执行任何更新,这可以阻止利用
8.3 密码泄露
可以通过对密码进行加密、解密避免明文保存密码,但是如果解密秘钥也容易暴露,这个方法就不完全有效了。
另外一个有效手段是,将数据库的访问限制在一个给定的网络地址集合中,通常是运行应用服务器的机器。
8.4 应用级认证
最简单的认证方式是密码,对银行等需要更加安全的方式,加密是其基础,后续将介绍。
许多应用程序提供双因素认证,两个独立的因素组合识别一个用户,这两个因素不能具有同样的弱点。例如输入双密码就不是好的策略,因为它们完全开源通过同样的方式被窃取。
在双因素认证的场景中,密码作为第一个因素,通过USB卡连接的智能卡或者其它可以用于加密技术的认证被广泛应用为第二个因素。比如一个动态生成伪随机数秘钥的设备,这需要设计合理的方案让设备的时钟和服务器的时钟同步的相当紧密。
第二个因素还广泛使用给用户绑定的手机号发送短信的方式。
双因素认证可能遭受中间人攻击,即通过将用户转到一个伪装得很好的中间网站窃取用户的密码(包括第二因素密码),并立即使用该密码到原始的应用程序中完成认证。HTTPS协议可以用来防止中间人攻击。
当用户访问同一个系统的多个网站时,未免会因为不得不在多个网站分别认证而感到不快,有的系统允许用户向一个中央认证服务进行认证,其他的网站或者应用程序会通过中央认证服务对用户进行认证。LDAP协议被广泛应用于实现这种认证的中央点。除了认证,还可以用中央认证服务统一存储用户的姓名、电子邮件等信息。
单点登录系统允许用户只认证一次,目前已经有对于web应用程序的实现可用。
安全断言标记语言(Security Assertion Markup Language,SAML)是一种在不同的安全域之间用于交换认证和授权信息的协议,以便提供跨阻止机构的单点登录。比如一个应用程序需要给所有耶鲁大学的学生提供访问,那么假设一位连接到该应用程序的用户具有诸如joe@yale.edu这样的用户名,就将该用户转向耶鲁大学的认证服务,而不直接对该用户进行认证。(联想下使用微信登录)
OpenId协议是用于跨阻止机构的单点登录的一种替代方案,OAuth协议允许用户通过共享授权令牌来对特定资源的访问进行授权。
8.5 应用级授权
虽然SQL存在一种相当灵活的角色授权系统,但是SQL授权模型在用户管理方面的作用还是非常受限的。主要原因是:
缺乏用户终端信息,与数据库打交道的主要是Web应用服务器而非用户终端。
缺乏细粒度授权。如果我们要每个学生只能看到自己的成绩,在目前SQL授权体系就不可能,因为SQL授权的细粒度只到达关系而非元组。当然我们可以借助视图实现需求,给每个学生创建一个视图,随后赋予其视图访问权限。但这实在太臃肿了。
通常情况下,授权任务是完全在应用程序中进行的。但应用授权也存在问题:
检查授权与其他逻辑混合
检查授权方式可能存在漏洞。如果一个应用程序某个部分未检查授权,则可能泄露数据。
通过SQL的细粒度授权,检查授权的“表面积”会小很多。一些数据库系统提供行级授权机制。比如Oracle的虚拟私有数据库(Virtual Private Database,VPD),其缺点是行级授权可能改变查询本意(详细内容请参考该专栏第5篇文章)
8.6 审计追踪
审计追踪(audit trail)是对于应用程序数据的更改和某些信息的日志,可以在系统安全性破坏或更新错误时进行原因追踪。
比如一个学生的成绩不正确,就可以检查审计日志,找出该成绩的更新记录,进行排查,还可以追踪到对应操作用户及其相关操作,看看它是否进行了非法操作。
部分数据库内部定义了内置机制创建审计追踪。也可以通过触发器来创建一个数据库级别的审计追踪。
应用级别通常还可以创建更高级别的审计追踪,用来跟踪元组级别的审计追踪,记录IP地址等细粒度信息。
防止审计追踪本身被破坏也是重要问题。一个方案是用一台无法被入侵的设备实时拷贝,后文将介绍区块链技术,这会是更有效的策略。
8.7 隐私
聚集的隐私数据在很多领域发挥作用,比如检测药物的副作用,这需要部分有效信息,但是又需要保护用户的数据。这需要合理选择信息。
很多应用程序提供隐私偏好设置给用户,让用户自主选择。
9.加密及其应用
9.1 加密技术
很多敏感数据可能被用作违法犯罪。必须对他们进行加密。加密算法如果设计的不巧妙,通过大量样本数据可以破解“规律”。
好的加密技术具有以下特点:
对于授权用户,加密和解密数据是相对简单的。
加密技术不应该依赖于算法的保密,而应该依赖于加密秘钥(encryption key)的算法参数。在对称秘钥(symmetric-key)加密技术里,加密秘钥也用于解密数据。在公钥(public-key,也称作非对称秘钥(symmetric-key))加密技术,存在公钥和私钥两种秘钥,分别用于加密和解密。
即使入侵者已经访问到加密的数据,但是确定其解密秘钥仍然是及其困难的。
高级加密标准(Advanced Encryption Standard,AES)是一种对称加密算法,于2000年成为了美国标准。
公钥加密采用公钥+私钥方式。公钥用于加密,在网络上共享,这样可以通过这种模式安全的交换信息。私钥为用户独有,用于解密。关于公钥私钥的技术细节可以单独查阅文档。
字典攻击(dictionary attack)的可能性使得对于诸如标识或者名称那样的小值加密(特别是使用公钥)变得复杂。比如如果需要对出生日期进行解密,只需要把出生日期依次用穷举法加密就可以破解。
可以在加密之前往值得末尾增加随机数,解密时移除的方法避免字典攻击,这种额外的位在AES中被称为初始化向量,在其它情况被称为salt位。
9.2 数据库中的加密支持
在数据库层面可以对磁盘、关系、属性等级别进行细粒度的加密支持,最小化解密的开销,同时不需要对应用程序进行修改。然而数据库通常不支持对主码、外码的加密,或者对加密属性添加索引。
9.3 加密和认证
基于密码的认证被广泛应用于操作系统和数据库系统,然而如果用户可以“嗅探”到网络上传送的密码,就可以非法入侵数据库。
在一种更安全的机制中涉及问答(challenge-response)系统,数据库系统发送寻字字符串,用户用一个密码作为加密秘钥对该寻字字符串进行加密,然后返回结果。数据库系统可以通过同样的密码将字符串解密并检查结果是不是和原始的询问字符创相同来验证用户的身份。这种方法确保没有密码会跨网络传输。
将私钥存储在个人计算机上是有风险的,智能卡提供了一种解决方案,可以将密码存储在嵌入式芯片上,智能卡的操作系统可以保证密码不会被读取。
9.3.1 数字签名
公钥加密的一个应用是数字签名,其私钥被用来签名,签名的公钥是公开的,任何人都可以对其进行认证。另外,除非私钥泄露,电子签名也可以确保不可否认性,即签字者的身份是本人。
9.3.2 数字证书
有一个认证相关的问题是,公钥保存在哪,如果存在网络上,又怎么确定公钥是真的,而不是被伪造的。对网站的认证认证可以通过数字证书系统来处理,其中公钥有一个其公钥公开的认证机构来签名。