前述总结
通过前述文章,我们目前手头的东西有些零散,我们有:一套html
页面、一个nginx下的html页面站点
、一个python制作的后台服务站点
,还有一些其他杂七杂八的东西。就像前述文章所说:
-
html文档
站点是通过nginx
的方式组建的,他的地址是http://127.0.0.1:8023/
,目前我们有一个demo1.html
页面。又如同我们之前所说,我们把页面也叫做前台。 -
python
站点是使用python
语言编写的,当然使用了python
的flask
包,他的地址是http://127.0.0.1:8120/
。目前里面至少有一个登录方法。我们把python这套程序,也叫做后台。
在真实情况下,前台与后台都是相互配合的。前台为用户提供的是画面、是可展示的东西;而后台,则为前台的正常运行,提供了数据、逻辑、算力等方面的支持。就我们目前的程序而言,我们如何让前台可以获取后台的数据呢?当然是通过http
协议。前台向后台发送http
请求,并根据后台的响应,做出自己的动作。
本文中,我们将会做起前后台的数据交互,并通过这个过程,简单理解同源策略
、为什么http请求是用户可干预的
以及session机制
。
一 前台的ajax请求
其实在浏览器上,你所请求的图片、页面、js和css等。都是http请求,只不过这些请求是无法实现动态刷新的,也就是需要跟随着整个页面一起去做请求。页面完成http
请求,有两种方式:
- 进行页面跳转(相当于整页刷新),在跳转时完成新的请求。
- 不依赖于整个页面的刷新,只进行局部的动作。当然并不代表请求完了之后不会做页面的跳转--XMLHttpRequest
我们今天要做的,是第二种,局部刷新请求(基于XMLHttpRequest的ajax)。我们手动实现这个功能会有很多代码,所以借助一个别人已经封装好的库去完成:jquery
。当然它不止能做ajax。
需要首先引入这个包,之后基本的用法如下
<script src="./js/jquery.js"></script>
<script>
$.ajax({
url:"请求目标(URI)",
data:"要传递的参数",
method:"http方法(POST GET)",
success:function(data){
"请求成功后,需要进行的处理"
}
})
</script>
回顾一下上篇中,我们调用python
服务的登录demo时发送的POST
报文。
POST /data/login HTTP/1.1
Host: 127.0.0.1:8120
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
username=admin&password=a123456
核心有四点:
- 使用
POST
方式 - URI是:
/data/login
- 服务器地址:
127.0.0.1:8120
- 传递两个参数:
username
和password
参照我们上面说的ajax
的用法,实现用户登录我们可以组织的代码如下:
// 获取用户输入的数据
var usname=document.querySelector("#usname").value;
var password=document.querySelector("#password").value;
// 发起ajax请求
$.ajax({
url:"http://127.0.0.1:8120/data/login",
data:{"username":usname,"password":password},
method:"POST",
success:function(data){
// 请求成功后,弹出响应信息
alert(data)
}
})
在HTML
页面中,依赖jquery
的http
请求,大概如此。但当我们真正去这样运行时,会发现无法收到响应。
这是因为一种安全策略--同源策略
。
二 同源策略
同源策略,是浏览器为了确保网页数据安全,执行的一种安全机制。
所谓同源,即同域名(ip)、同协议、同端口。当两个页面具有相同的以上三种信息时,浏览器则认为其是同源(同域)的;当有一项不同,则认为其是不同源(跨域)的。对于不同源的项,浏览器会做出一些限制,比如:
- 禁止不同源的网页,相互之间操作彼此的DOM结构;
- 禁止不同源的网页,相互之间访问彼此的页面存储;
- 禁止不同源的网页(站),发起XMLHttpRequest(
ajax请求
)。
上述案例中,我们的页面是127.0.0.1:8023
下的,而试图对127.0.0.1:8120
发起ajax请求
。显然端口不同,违反了同源策略的。要让网页可以调取到后台数据,有两个解决方案:
- 让被请求站点
127.0.0.1:8120
告诉浏览器,同意接收非同源页面发起的请求,我们叫做跨域访问
。 - 让被请求站点
127.0.0.1:8120
和发起请求的网页127.0.0.1:8023
位于同一个站点下。
我们下面将使用第二种方案,将两个站点整合为一个。如果使用第一种方案,将有可能造成“跨站点请求伪造(CSRF)”攻击,后续我们会对此进行讲解。
三 利用nginx整合站点
前述文章中,我们使用nginx
让网页“跑了起来”,现在,我们还需要借助nginx去进行两个站点的整合。
依然需要打开nginx的配置文件./nginx.conf
,以下是我们之前的配置内容
server {
listen 8023;
server_name localhost;
location / {
root D:/html5_code_injection/;
}
}
现在,我们在原来的基础上,扩展一个路径的配置,并将其映射到我们的 Python服务127.0.0.1:8120
server {
listen 8023;
server_name localhost;
location / {
root D:/html5_code_injection/;
}
location /data/
{
proxy_pass http://127.0.0.1:8120;
}
}
这样,所有经过127.0.0.1:8023
指向/data/........
的请求,均会被转发到127.0.0.1:8120
。我们的站点结构变成了如下所示:
web请求统一发送到8023
端口,nginx
会将所有请求默认转到HTML
文档的文件夹;URI
以/data
开头的请求,则会被转发到8120
端口,也即python后台
。
这样,我们刚才在页面上实现的js
代码,也需要进行调整。将url
部分改成本站点的相对路径
最终,我们完整版的,js端的登录请求方法如下
function login()
{
// 获取用户输入的用户名及密码
var usname=document.querySelector("#usname").value;
var password=document.querySelector("#password").value;
// 进行本地存储,这一步可以略过
localStorage.setItem("usname",usname);
localStorage.setItem("password",password);
// 向后台发送请求,验证用户名和密码是否正确
$.ajax({
url:"/data/login",
data:{username:usname,password:password},
method:"POST",
success:function(data){
alert(data);
},
error:function(data)
{
console.log(data);
}
})
}
从页面上,已经可以操作登录
四 如何证明“你是你”
通过前述的一系列操作,我们已经完成了一个通过前后台互动,实现的登录功能;甚至通过文件,实现了对“用户名”和“密码”的存储。但接下来,我们就会面临另一个问题:“如何证明"你是你"”。
为了体验这个问题,我们需要把我们的程序添加一个新的功能:登录请求完成后,我们要通过另一个请求,获取登录者的个人信息。在进行这项体验的过程中,我们也会明白,为什么说web请求不可靠。
下面是我们的步骤:
-
创建两个文件,用于存储两个用户的信息,一个为
admin.dat
另一个为xiaohong.dat
。格式内容都可以随意,我所存入的信息:
admin.dat
的内容为:{ "name":"小明", "age": 12 }
xiaohong.dat
的内容为:{ "name":"小红", "age": 11 }
-
我们需要在前台页面上,添加一个
button
,当被点下时,调用后台的方法,获取登录用户的信息
我们需要在后台python程序中,添加一个接口,供前台调用,这个接口的作用,就是读取上面我们两个用户文件中的数据,之后返回给前台。
我们有两个用户文件:admin.dat
和xiaohong.dat
,如果python
程序的代码如下,应该怎么确定要打开哪个文件去读取信息呢?
这个问题的解决,也有两种途径:
- 用户是在前台登录的,前台自然可以记录下用户名,之后
获取信息
按钮点击后,前台在发起的ajax
请求中,添加入用户名
的信息。相当于你每次来拜访我,都提供一个名片,我每次根据名片知道你是谁。
- 用户登录是需要经过后台认证的,当后台认证通过后,通过某种形式,将用户名记录在后台程序范围内,之后将所记录的信息通过某种形式和真正的用户(浏览器)进行绑定。相当于你把身份证复印件放我这,每次来我核对复印件。这种方式我们稍后详细介绍。
这两种方式,我们分别实现后进行体验。
4.1 前台记录用户信息方式
首先我们需要确认前台页面(js
)的登录方法。确认方法中使用了
localStorage.setItem("usname",usname);
localStorage.setItem("password",password);
这两句的意思,是在页面存储中,存入用户名和密码。
之后,需要在前台页面(
js
)新建一个获取用户信息的方法,这个方法需要:
- 读取页面存储的用户名(
usname
) - 向后台接口发起
ajax
请求,并且携带用户名作为参数 - 显示调取到的信息
所以,这个方法的代码我们定义为:function get_info() { // 读取本地存储中的usname unm=localStorage.getItem("usname"); // 发起ajax请求 $.ajax({ url:"/data/info", // 参数携带了本地存储的用户名 data:{uname:unm}, method:"GET", success:function(data){ // 请求成功后,弹窗显示响应信息 alert(data); }, error:function(data) { console.log(data); } }) }
- 需要在
python
端定义一个接口,接受http
路径为/data/info
的GET
请求,并且接受一个uname
参数,根据这个参数去读取本地的用户文件,所以,python的代码如下:# 定义访问路径为 /data/info 的get请求 @app.route('/data/info',methods=['get']) def get_my_info(): # 读取 请求head 中的uname参数 uname=request.args.get("uname") # 根据前台传递的参数,打开本地用户文件,需要使用UTF8方式打开,避免中文乱码 with open("./"+uname+".dat",encoding="utf8") as f: txt=f.read() # 返回读取的内容 return txt
更改完成后,功能效果如下
然而,我们说。
接下来我们演示一下前端传参的不可靠性。
我们依然采用admin
账号登录,正如上述所示,admin
的姓名为小明
,如果通过某些操作,可以获取到服务器上的xiaohong
账号的信息,那么就可以看到前台输入的不可靠性。
所以这种流程是不可靠的,接下来,我们来尝试第二种方案,用python
后台程序记录用户信息。
4.2 session机制
4.2.1 为程序添加session机制
在web开发过程中,提供了多种技术,将服务端和客户端“绑定”在一起,session就是其中一种。目前,你可以理解为这种机制是以浏览器为准去判断的;这种机制可以针对每个客户端(浏览器)进行身份绑定,也就是说:我用admin
去登录后,如果启用了session机制,那么之后我在同一个浏览器里发送的每个请求,浏览器都可以自动识别为是admin
。
稍后我们会去简单介绍原理,目前我们先对程序进行修改,添加入session机制后进行体验。在上面的基础上,我们最主要是需要对python
后台程序做修改:
- 修改
python
后台的my_login()
方法,加入session
机制
- 修改
python
后台的get_my_info()
方法,将原来从request
中获取参数的部分,改为从session
中提取用户信息
- 前台页面(
js
)中,get_info()
方法,不再传送用户名作为参数。当然,不改也没太大影响
经过如此的修改,我们在进行信息获取
时就不会再通过页面进行参数传递,所以“不再存在”前面我们提到的问题。之所以打上引号,其实用户还是可以进行一定程度的干预的。我们先暂时不去考虑,简单介绍一下session机制是如何实现的。
4.2.2 session极简原理
通过信息获取
流程,我们首先来看看,与未加入session
机制时相比,http
通讯过程是否存在了什么不同。
这是未添加session机制时的请求头
这是添加session机制后的请求头
我们可以看到,在请求头里,多了一项,叫
Cookie
,值是一串“乱七八糟”的数据(eyJ1c25hbWUiOiJhZG1pbiJ9.ZP6D6g.0yEMfRRLiMxwLoYviq05zumqQpA
),我们暂时不去解释这串数据的含义,目前我们可以把它理解为一串不会重复的随机数。而我们的python
后台程序,其实就是通过这一串数据去识别浏览器的身份的,在后台中,会将这一串数据,与你存储在session
对象中的内容进行绑定。所以,虽然我们明面上没有传递什么参数,实际在request head里面,浏览器会悄悄给我们附带上这一串数据。那么,的呢?上文中,我们初始加入
session
是在登录过程中。我们来看一下加入session后,登录方法的响应头
可以看到,
响应头
中有一个Set-Cookie
项,它后面对应的,就是上面那串数。浏览器在识别到这个关键字后,就会保存下它后面的字符串,之后,会为的所有请求,以Cookie
的形式将这串字符串附带到request head
中。
因为我们是在登录过程中,向session中加入了用户信息,所以在其他方法中去验证session中的信息,还可以起到验证用户登录的作用。如果用户没有登录,那么session中肯定是没有信息的。