(Web安全与防御通俗基础 四) 前后台数据交互 同源策略 为什么http请求是用户可干预的 session机制

前述总结

通过前述文章,我们目前手头的东西有些零散,我们有:一套html页面、一个nginx下的html页面站点、一个python制作的后台服务站点,还有一些其他杂七杂八的东西。就像前述文章所说:

  1. html文档站点是通过nginx的方式组建的,他的地址是http://127.0.0.1:8023/,目前我们有一个demo1.html页面。又如同我们之前所说,我们把页面也叫做前台。
  2. python站点是使用python语言编写的,当然使用了pythonflask包,他的地址是http://127.0.0.1:8120/。目前里面至少有一个登录方法。我们把python这套程序,也叫做后台。

在真实情况下,前台与后台都是相互配合的。前台为用户提供的是画面、是可展示的东西;而后台,则为前台的正常运行,提供了数据、逻辑、算力等方面的支持。就我们目前的程序而言,我们如何让前台可以获取后台的数据呢?当然是通过http协议。前台向后台发送http请求,并根据后台的响应,做出自己的动作。

本文中,我们将会做起前后台的数据交互,并通过这个过程,简单理解同源策略为什么http请求是用户可干预的以及session机制

一 前台的ajax请求

其实在浏览器上,你所请求的图片、页面、js和css等。都是http请求,只不过这些请求是无法实现动态刷新的,也就是需要跟随着整个页面一起去做请求。页面完成http请求,有两种方式:

  1. 进行页面跳转(相当于整页刷新),在跳转时完成新的请求。
  2. 不依赖于整个页面的刷新,只进行局部的动作。当然并不代表请求完了之后不会做页面的跳转--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

核心有四点:

  1. 使用POST方式
  2. URI是:/data/login
  3. 服务器地址:127.0.0.1:8120
  4. 传递两个参数:usernamepassword

参照我们上面说的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页面中,依赖jqueryhttp请求,大概如此。但当我们真正去这样运行时,会发现无法收到响应

无响应信息

这是因为一种安全策略--同源策略

二 同源策略

同源策略,是浏览器为了确保网页数据安全,执行的一种安全机制。


同源策略

所谓同源,即同域名(ip)、同协议、同端口。当两个页面具有相同的以上三种信息时,浏览器则认为其是同源(同域)的;当有一项不同,则认为其是不同源(跨域)的。对于不同源的项,浏览器会做出一些限制,比如:

  1. 禁止不同源的网页,相互之间操作彼此的DOM结构;
  2. 禁止不同源的网页,相互之间访问彼此的页面存储;
  3. 禁止不同源的网页(站),发起XMLHttpRequest(ajax请求)。

上述案例中,我们的页面是127.0.0.1:8023下的,而试图对127.0.0.1:8120发起ajax请求。显然端口不同,违反了同源策略的。要让网页可以调取到后台数据,有两个解决方案:

  1. 让被请求站点127.0.0.1:8120告诉浏览器,同意接收非同源页面发起的请求,我们叫做跨域访问
  2. 让被请求站点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请求不可靠

下面是我们的步骤:

  1. 创建两个文件,用于存储两个用户的信息,一个为admin.dat另一个为xiaohong.dat。格式内容都可以随意,我所存入的信息:
    admin.dat的内容为:

    {
     "name":"小明",
     "age": 12
    }
    

    xiaohong.dat的内容为:

    {
     "name":"小红",
     "age": 11
    }
    
  2. 我们需要在前台页面上,添加一个button,当被点下时,调用后台的方法,获取登录用户的信息

    页面添加按钮

  3. 我们需要在后台python程序中,添加一个接口,供前台调用,这个接口的作用,就是读取上面我们两个用户文件中的数据,之后返回给前台。

\color{red}{从后台python程序的角度,试想一下有什么问题?}
我们有两个用户文件:admin.datxiaohong.dat,如果python程序的代码如下,应该怎么确定要打开哪个文件去读取信息呢?

python代码

这个问题的解决,也有两种途径:

  1. 用户是在前台登录的,前台自然可以记录下用户名,之后获取信息按钮点击后,前台在发起的ajax请求中,添加入用户名的信息。相当于你每次来拜访我,都提供一个名片,我每次根据名片知道你是谁。
    前台记录方式示意
  2. 用户登录是需要经过后台认证的,当后台认证通过后,通过某种形式,将用户名记录在后台程序范围内,之后将所记录的信息通过某种形式和真正的用户(浏览器)进行绑定。相当于你把身份证复印件放我这,每次来我核对复印件。这种方式我们稍后详细介绍。

这两种方式,我们分别实现后进行体验。

4.1 前台记录用户信息方式

首先我们需要确认前台页面(js)的登录方法。确认方法中使用了

localStorage.setItem("usname",usname);
localStorage.setItem("password",password);

这两句的意思,是在页面存储中,存入用户名和密码。

js登录方法

之后,需要在前台页面(js)新建一个获取用户信息的方法,这个方法需要:

  1. 读取页面存储的用户名(usname)
  2. 向后台接口发起ajax请求,并且携带用户名作为参数
  3. 显示调取到的信息
    所以,这个方法的代码我们定义为:
     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);
             }
         })
     }
    
  4. 需要在python端定义一个接口,接受http路径为/data/infoGET请求,并且接受一个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
    

更改完成后,功能效果如下


正常流程

然而,我们说\color{red}{用户可以干预客户与服务器之间传送的所有数据,服务器程序必须假设所有输入的信息都是恶意的输入}
接下来我们演示一下前端传参的不可靠性。
我们依然采用admin账号登录,正如上述所示,admin的姓名为小明,如果通过某些操作,可以获取到服务器上的xiaohong账号的信息,那么就可以看到前台输入的不可靠性。

用户干预流程

所以这种流程是不可靠的,接下来,我们来尝试第二种方案,用python后台程序记录用户信息。

4.2 session机制

4.2.1 为程序添加session机制

在web开发过程中,提供了多种技术,将服务端和客户端“绑定”在一起,session就是其中一种。目前,你可以理解为这种机制是以浏览器为准去判断的;这种机制可以针对每个客户端(浏览器)进行身份绑定,也就是说:我用admin去登录后,如果启用了session机制,那么之后我在同一个浏览器里发送的每个请求,浏览器都可以自动识别为是admin
稍后我们会去简单介绍原理,目前我们先对程序进行修改,添加入session机制后进行体验。在上面的基础上,我们最主要是需要对python后台程序做修改:

  1. 修改python后台的my_login()方法,加入session机制
    my_login方法的修改
  2. 修改python后台的get_my_info()方法,将原来从request中获取参数的部分,改为从session中提取用户信息
    get_my_info方法的修改
  3. 前台页面(js)中,get_info()方法,不再传送用户名作为参数。当然,不改也没太大影响

经过如此的修改,我们在进行信息获取时就不会再通过页面进行参数传递,所以“不再存在”前面我们提到的问题。之所以打上引号,其实用户还是可以进行一定程度的干预的。我们先暂时不去考虑,简单介绍一下session机制是如何实现的。

4.2.2 session极简原理

通过信息获取流程,我们首先来看看,与未加入session机制时相比,http通讯过程是否存在了什么不同。
这是未添加session机制时的请求头

未添加session

这是添加session机制后的请求头
添加session

我们可以看到,在请求头里,多了一项,叫Cookie,值是一串“乱七八糟”的数据(eyJ1c25hbWUiOiJhZG1pbiJ9.ZP6D6g.0yEMfRRLiMxwLoYviq05zumqQpA),我们暂时不去解释这串数据的含义,目前我们可以把它理解为一串不会重复的随机数。而我们的python后台程序,其实就是通过这一串数据去识别浏览器的身份的,在后台中,会将这一串数据,与你存储在session对象中的内容进行绑定。所以,虽然我们明面上没有传递什么参数,实际在request head里面,浏览器会悄悄给我们附带上这一串数据
那么,\color{red}{浏览器是怎么知道什么时候,设置什么字符串}的呢?上文中,我们初始加入session是在登录过程中。我们来看一下加入session后,登录方法的响应头
添加session功能 登录方法的响应头

可以看到,响应头中有一个Set-Cookie项,它后面对应的,就是上面那串数。浏览器在识别到这个关键字后,就会保存下它后面的字符串,之后,会为\color{red}{同源}的所有请求,以Cookie的形式将这串字符串附带到request head中。

因为我们是在登录过程中,向session中加入了用户信息,所以在其他方法中去验证session中的信息,还可以起到验证用户登录的作用。如果用户没有登录,那么session中肯定是没有信息的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容