前 言
🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
☕专栏简介:相当硬核,黑皮书《数据库系统概念》读书笔记,讲解:
1.数据库系统的基本概念(数据库设计过程、关系型数据库理论、数据库应用的设计与开发…)
2.大数据分析(大数据存储系统,键值存储,Nosql系统,MapReduce,Apache Spark,流数据和图数据库等…)
3.数据库系统的实现技术(数据存储结构,缓冲区管理,索引结构,查询执行算法,查询优化算法,事务的原子性、一致性、隔离型、持久性等基本概念,并发控制与故障恢复技术…)
4.并行和分布式数据库(集中式、客户-服务器、并行和分布式,基于云系统的计算机体系结构…)
5.更多数据库高级主题(LSM树及其变种、位图索引、空间索引、动态散列等索引结构的拓展,高级应用开发中的性能调整,应用程序移植和标准化,数据库与区块链等…)
🌰 文章简介:这篇文章将介绍如何使用数据库开发应用程序。
1.应用程序和用户界面
互联网用户和数据库往往不会直接打交道,而是通过应用程序对数据库进行间接访问。
在计算机发展早期,应用程序在大型主计算机上运行,用户通过终端与应用程序交互。
个人计算机的发展导致了带有图形的用户界面GUI的数据库应用的发展。程序在个人计算机上运行,这些代码直接与一个共享的数据库进行通信。这种模式被称为客户-服务器体系结构。
这种模式至少有两个问题:
- 用户机器可以直接访问数据库,从而带来安全性问题。
- 维护困难。对应用程序或数据库的任何更改(扩展、更新、修改等)都要求位于客户计算机上的应用程序的所有副本一起更改(重新部署软件)。
现在有两种方法用于避免上述问题。
-browser/server。web浏览器提供前端,通过前端访问后端。这样就不需要单独在客户机安装、维护软件。同时,与c语言编写的程序不同,前端的脚本语言JavaScript可以运行在安全模式下,保证不会导致安全问题。
- 应用程序安装在独立设备上。这些设备主要是移动设备,它们通过API与后端应用程序进行通信,并不能直接访问数据库。
2.Web基础
2.1 同一资源定位符
统一资源定位符(Uniform Resource Locator)是web上可以访问的每份文档的全球唯一名称。比如:
http://www.acm.org/sigmod
上面的URL由三部分组成,http是超文本传输协议,“https”是“http”的安全版本,并且是当今的首选模式。第二部分是一台具有web服务器的机器名称。第三部分是该机器上文档的路径或者唯一标识。
URL还可以包含位于web服务器上程序的标识,以及传递给该程序的参数。并由该程序返回一个html文档给web服务器。
http://www.google.com/search?q=silberschatz
2.2 超文本标记语言
下图就是一个html创建一个表单的过程
Http定义了两种请求方式,上图所示的get请求将请求参数作为URL的一部分,另外一种请求Post请求则发送一个请求,将参数值作为web服务器和浏览器之间交换的HTTP协议的一部分发送。
有些编辑器支持使用图形界面来直接创建HTML文本编辑器。
CSS支持给html提供样式。
HTML5支持多种形式的输入类型,比如日期和时间选择,文件选择,还支持对输入采取限制(最大值,最小值等)
2.3 web服务器和会话
Web服务器是运行于服务机上的程序,它接收浏览器的请求,根据其提供的参数执行程序,最后将结果以HTML文档的形式将结果传送回去。
下图显示了一个使用三层体系结构搭建的web应用程序。通用网关接口(CGI)标准定义了web服务器如何与应用程序进行通信。使用多层服务器增加了系统的开销,CGI接口为每个请求都启动一个新的进程为之服务,这导致了更大的开销。
因此目前大部分的应用程序将web服务器和应用服务器合二为一,采用两层web应用程序体系结构。
用户通过JDBC或者ODBC来访问数据库时,则会建立一个会话,会话信息会一直保存,直到该会话终止。但是客户端和web服务器之间不存在长连接,往往是连接-请求-响应-关闭连接的方式,这是为了更多的容载海量的用户访问,降低连接限制带来的影响。
尽管连接会关闭,但是web应用程序也需要会话信息来允许有意义的用户交互,对用户进行认证等。它的策略是,会对每一次会话进行一次用户认证,会话的进一步交互无需进行认证。
为了实现会话,需要在客户端存储额外的信息。这些额外的信息通常以cookie的形式维护在客户端,一个cookie是一段包含标识信息的文本,并且与一个名称相关联。例如,google.com可能设置一个名为prefs的cookie,它对用户的偏好设置进行编码,比如语言偏好和每页显示的结果数目,对于每个搜索请求,google.com都能够从用户的浏览器得到这个名为prefs的cookie,然后根据其指定的偏好来显示结果。一个域(Web站点)只允许获取它自己设置的cookie,而不能获取其他域所设置的cookie,而且cookie的名称可以跨域重用。
为了跟踪用户会话,服务端会生产一个名为seesionid
的cookie,这是一个特殊的cookie,它用于区分不同的会话,因此也会在服务端存储。当一个请求进来时,应用服务器从客户端请求名为seesionid的cookie,如果客户端没有存储该cookie,或者返回的值与服务端存储的有效会话标识不同,就认为该请求不是当前会话的一部分。
对于安全性要求不高的应用,比如公共新闻站点,cookie可以永久的存储在浏览器端和服务器段。他们识别初用户对一个站点的后续访问,而不需要输入任何验证信息。
对于安全性高的应用,则可能会设置时间限制,在超时后或者用户注销(退出登录)时使会话失效,使会话失效其实就是将会话标识从服务端删除。
3.Servlet
java servlet(Java服务端程序)规范定义了一种用于在WEB/应用服务器与应用程序之间进行通信的应用编程接口。Java的HttpServlet类实现了Servlet API的规范。
3.1 Servlet示例
我们卡妈妈提到了如下的一个表单请求。
现在假设该请求被提交给后端,我们编写下对应的后端处理逻辑代码,看看后端要怎么应对前端的请求
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
@WebServlet("PersonQuery")
public class PersonQueryServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResonse response)
throws ServletException, IOException
{
response.setContentType("text/html");
PrintWriter out = resonse.getWriter();
...检查用户是否已登录...
out.println("<HEAD><TITLE>Query Result</TITLE></HEAD>");
out.println("<BODY>");
String persontype = request.getParameter("persontype");
String name = request.getParameter("name");
if(persontype.equals("student")) {
...寻找具有指定姓名的学生的代码...
...使用JDBC与数据库进行通信...
...假设已获取到ResultSet rs,并且...
...包含属性:ID、姓名与系名...
String headers = new String[]{"ID","Name","Department Name"};
Util::resultSetToHtml(rs,headers,out);
}
else {
...同上,但是对于教师的...
}
out.println("</Body>");
out.close();
}
}
我们终于知道服务器对HTTP请求的原理了,当服务器接收到请求来执行一个特定的servlet
时,servlet
的代码被加载到Web/应用服务器中,servlet的任务就是处理这种请求,访问数据库以检索出必要的信息,并动态生成一个HTML页面返回给客户端浏览器。
我们注意到,前端指定了ation = "PersonQuery"
,而后端则使用注解@WebServlet("PersonQuery")
来显示的注释当前servlet是用来处理对PersonQuery
的请求的。而且前端的表单指定使用HTTP的Get
机制,因此servlet的doGet()方法将会被执行。
每次servlet请求都导致在执行调用的内部生成一个新的线程,因此多个请求就可以被并行处理。
请求将cookie和参数放入一个HttpServletRequest对象 中,后端通过api对其进行提取,根据提取参数执行数据库的查询工作,最后将其通过HttpServletResonse
对象返回。
结果是这样输出给response的,我们通过它获取了一个PrintWriter
对象,将要返回的html通过该对象输出,其中查询到的数据输出的方式是Util.resultSetToHtml()
实现的。其参考代码如下。
3.2 Sevlet会话
cookie可以用来识别一个请求与前一个请求是否来自同一个浏览器会话。其在后端servlet处理的逻辑是怎么样的呢?
servlet的API中提供了跟踪会话技术的方法。调用HttpServletRequest
中的getSession(false)
可以获取来自浏览器的HttpSession
对象。当该方法被调用是,将会首先要求哦i客户端返回一个具有指定名称的cookie,如果没有该cookie,则说明该请求不是正在进行的会话的一部分。
此时getSession
会返回一个空值,引导用户到登入页面。登录页面允许用户提供用户名和密码,登录页面所对象的servlet
会验证用户的信息。
如果用户通过认证,登录servlet会话会执行getSession(true)
,这个方法会创建一个新的会话。为了创建一个新的会话,服务器内部会执行如下任务:在客户端浏览器中设置一个cookie(比如名为sessionId),该cookie用会话标识作为它所关联的值。创建一个新的会话对象,并将会话标识的值与该会话对象相关联。
servlet
代码还能够在HttpSession
对象中存储和查找(属性,值)对,以便在一个会话内的多个请求之间维持状态。比如,用户通过登录认证并且会话对象被创建之后,可以将userid
存储为会话的一个参数,标记该用户已经通过了登录认证:
session.setAttribute("userid",userid)
那么我们只需要取出userid
,就可以判断会话用户有没有通过登录认证。如果没有,就可以通过下面的方式让其回到登录页。
为了详细说明上面的内容,我们看如下登录案例(转载)。
需要的页面:
login.jsp:登录页面,提供登录表单;
index1.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
index2.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
Servlet:
LoginServlet:在login.jsp页面提交表单时,请求本Servlet。在本Servlet中获取用户名、密码进行校验,如果用户名、密码错误,显示“用户名或密码错误”,如果正确保存用户名session中,然后重定向到index1.jsp;
当用户没有登录时访问index1.jsp或index2.jsp,显示“您还没有登录”。如果用户在login.jsp登录成功后到达index1.jsp页面会显示当前用户名,而且不用再次登录去访问index2.jsp也会显示用户名。因为多次请求在一个会话范围,index1.jsp和index2.jsp都会到session中获取用户名,session对象在一个会话中是相同的,所以都可以获取到用户名!
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>login.jsp</title>
</head>
<body>
<h1>login.jsp</h1>
<hr/>
<form action="/day06_4/LoginServlet" method="post">
用户名:<input type="text" name="username" /><br/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
index1.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index1.jsp</title>
</head>
<body>
<h1>index1.jsp</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
out.print("您还没有登录!");
} else {
out.print("用户名:" + username);
}
%>
<hr/>
<a href="/day06_4/index2.jsp">index2</a>
</body>
</html>
index2.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index2.jsp</title>
</head>
<body>
<h1>index2.jsp</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
out.print("您还没有登录!");
} else {
out.print("用户名:" + username);
}
%>
<hr/>
<a href="/day06_4/index1.jsp">index1</a>
</body>
</html>
LoginServlet
public class LoginServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
if(username.equalsIgnoreCase("cloud")) {
response.getWriter().print("用户名或密码错误!");
} else {
HttpSession session = request.getSession();
session.setAttribute("username", username);
response.sendRedirect("/day06_4/index1.jsp");
}
}
}
3.3 Servlet的生命周期
servlet的生命周期由部署它的web/应用服务器来控制,当由客户端请求一个特定的servlet时,服务器首先检查是否存在该servlet的一个实例。如果不存在,web服务器就将servlet类加载进java虚拟机,并创建一个servlet类的实例。另外,服务器调用init()
方法来初始化该servlet实例。每个servlet实例仅在它被加载的时候被初始化一次。
在确定servlet实例存在后,服务器调用servlet的service
方法,并以一个request
对象和一个response
对象作为参数,在缺省的情况下,服务器创建一个新的线程执行service方法,因此,一个servlet上的多个请求就可以并行执行,而不必等待之前的线程执行完。service方法视情况调用doGet
或doPost
.
当不再需要的时候,可以通过调用destroy()
方法来关闭一个servlet。服务器可以设置一个超时时限,如果在超时时限内没有对一个servlet进行过一个请求,则自动关闭该servlet。超时实现是一个参数,可以根据应用来适当的对它进行设置。
3.4 应用服务器
最知名的servlet应用服务器是Apache的Tomcat。
开发Servlet的应用程序的最佳方式是使用Idea,eclipse等Ide编辑器,他们内置有Tomcat服务器。
除了最基本的servlet支持之外,应用服务器通常还提供了各种有用的服务,它们允许应用程序被部署或者被停止,并且它们提供了监控应用服务器的功能,包括性能监控。还支持跨多个应用服务器的并行处理,处理对象等。
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”服务对参数和结果使用XML编码,使用一种专门的规范来定义Web API,并使用在HTTP协议之上构建的一个协议层。
5.3 断连操作
很多应用希望即使客户端与服务段断开,仍然支持某些操作。例如,一个学生填写一个申请表单,但是他没有提交的情况下断网了,他希望网络恢复时填写的表单内容还能恢复。构建这种应用需要客户端机器中的本地存储。
HTML5标准支持本地存储。本地存储可以使用JavaScript来访问。
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 数字证书
有一个认证相关的问题是,公钥保存在哪,如果存在网络上,又怎么确定公钥是真的,而不是被伪造的。对网站的认证认证可以通过数字证书系统来处理,其中公钥有一个其公钥公开的认证机构来签名。
数字证书被用来广泛的用于用户认证网站。数字证书也可以用于用户认证。