AJAX 封装 + 前端安全

342 阅读14分钟

什么是AJAX?

AJAX是Asynchronous javascript and xml 的缩写,用javascript以异步的形式操作xml(现在操作的是json).

  • 随着谷歌地图的横空,这种不需要刷新页面就可以与服务器通讯的的方式,很快被人们所知。

  • 在传统的web模型当中,客户端向服务器发送一个请求,服务器就会返回整个页面

  • 我们前面学习的form表单来传输数据的方式就属于传统的web模型,当我们点击submit按钮之后,整个页面就会被刷新一下。

  • form表单有三个很重要的属性,分别是methodactionenctype

  • method,一般是get或者post;

  • action是我们要把数据传送到的地址;

  • enctype则是是否对数据进行编码

  • enctype的默认值是 “application/x-www-from-urlencoded”,即在发送前编码所有字符,这个属性值即使我们不写也是默认这个的。

  • 但是当我们在使用包含文件上传控件的表单的时候,这个值就必须更改成“multipart/from-data”,即不对字符进行编码。

  • 而在AJAX模型中,数据在客户端和服务器之间独立传输,服务器不在返回整个页面

AJAX优点

  • 1、页面没有刷新,在页面内容内和服务器通信,给用户的体验好
  • 2、通过异步的形式与服务器通信,不需要打断用户操作,给用户的体验更好
  • 3、减轻服务器负担
  • 4、不需要插件和小程序

AJAX缺点

1.不支持浏览器的后退。
2.安全问题,xss跨站点脚本攻击、sql注入攻击。
3.对搜索引擎支持较差seo。
4. 不支持移动端设备。
5. 违背url 和资源定位的初衷。

常见的Web攻击方式:SQL注入、XSS跨站脚本攻击、CSRF跨站点请求伪造

1、sql注入

原理

sql 注入就是通过sql命令插入到web 表单提交输入域名页面请求的查询字符串, 最终达到欺骗服务器执行恶意的sql命令。 登陆操作sql:

  • select * from student where username=‘输入的用户名’ and password=‘输入的密码’

  • 输入的用户名--> ' or '1' = '1' or '1' = '1

  • 输入的密码--> 1 ' or '1 '=' 1

  • select * from student where username='' or '1' = '1' or '1' = '1' and password= "

  • select * from student where username='' and password='1' or '1'='1'

以上两个SQL语句的where条件永远是成立的,所以验证永远是有效的.

输入的用户名 --> tom' ; drop table student- -

  • select * from student where username='tom' ; drop table student--' and password=''
  • 直接把student表给删除了(双连接符表示注释);

防御

1、不相信用户的输入,对用户的输入校验、可以通过正则表达式(必须数字,特定字符串,电话,email的正则),或限制长度;对单引号和双“-”进行转换等

2、永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询``存取。哪怕参数是常量,也请用预编译语句PreparedStatement同时用占位符,如: “select * from table where comment like ?”。

注意:如果参数不是使用的占位符,即使用PreparedStatement执行时也并不是预编译。

3、永远不要使用管理员权限的数据库连接

每个应用使用单独的有权限的数据库连接,这样能降低数据库密码被泄漏而带来的破坏。

4、不要把机密信息直接存放

加密或者hash掉密码敏感的信息;如数据库连接密码用户密码设备密码需要加密存储

2 XSS跨站脚本攻击

2.1 原理

XSS(Cross Site Scripting)跨站脚本攻击

  • 指攻击者在网页中嵌入客户端脚本(例如JavaScript)
  • 当用户浏览此网页时,脚本就会在用户的浏览器上执行,从而达到攻击者的目的,比如获取用户的Cookie,导航到恶意网站,携带木马等。

1、Dom-Based XSS 漏洞

基于DOM的XSS,也就是web server不参与,仅仅涉及到浏览器的XSS. 攻击过程如下:小A发现了test.com中的Search.jsp页面有XSS漏洞,Search.jsp的代码如下:

<html>
    <title></title> 
	<body> 
    	Results  for  <%=request.getParameter("term")%>...  
	</body>  
</html> 

a准备: 1、偷取信息网址badboy.com 2、恶意url: test.com/search.asp?…)

方式: (邮件,QQ) 等方式给到 用户b. 小B点击了这个URL,嵌入在URL中的恶意Javascript代码就会在小B的浏览器中执行,那么小B在test.com网站cookie,就会被发送到badboy网站中,这样小B在test.com的信息就被小A盗了

2、Stored XSS(存储式XSS漏洞)

如一个应用程序从数据库中查询数据,在页面中显示出来,攻击者这个页面输入恶意的脚本数据后,用户浏览此类页面时就可能受到攻击,使得所有访问该页面的用户都面临信息泄露的可能.

小C发现了网站A上有一个XSS 漏洞,该漏洞允许将攻击代码保存在数据库中,于是小C发布了一篇文章,文章中嵌入了恶意JavaScript代码。其他人如小D访问这片文章的时候,嵌入在文章中的恶意Javascript代码就会在小D的浏览器中执行,其会话cookie或者其他信息将被小C盗走。

Dom-Based XSS漏洞威胁用户个体,而存储式XSS漏洞所威胁的对象将是大量的用户。

防御措施

1、将重要的cookie标记为http only, 如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。

2、输入过滤校验,对用户提交的数据进行有效性验证,仅接受指定长度范围内并符合我们期望格式的的内容提交。 过滤一些些常见的敏感字符,例如:< > ‘ “ & # \;过滤或移除特殊的Html标签, 例如: <script>, <iframe> , < for <, > for >, &quot for;过滤JavaScript 事件的标签,例如 "οnclick=", "onfocus" 等等。这里的数据校验除了前台要做,后台也要做。

3、DOM型的XSS攻击防御   把变量输出到页面时要做好相关的编码转义工作. 要输出到 <script>中,可以进行JS编码;要输出到HTML内容或属性,则进行HTML编码处理。根据不同的语境采用不同的编码处理方式。

3 CSRF跨站点请求伪造

CSRF( Cross-site request forgery )尽管听起来很像XSS跨站脚本攻击,CSRF则是通过伪装用户的请求来访问一个用户已经认证的站点,从而在并未授权的情况下执行特定的操作。与XSS相比,CSRF攻击不大流行和难以防范,所以比XSS更具危险性。

image.png

防范

1.目前主流的做法是使用CSRF Token抵御CSRF攻击,在每个HTTP请求中添加一个随机生成的Token值,一般名为_csrf并且是hidden的,然后由服务端验证,验证通过才是有效的请求。

注意:_csrf的token值一般放在请求参数或请求头里,不能放在cookie中,因为cookie包含在浏览器中可以被获取到。还有我们一般在更新操作才携带这个CSRF Token,这样不会影响外部访问我们网站的查询操作,也不容易导致这个token泄漏,注意要求CSRF Token的更新操作要设计成幂等的。

2.还有一种新兴的方法是使用cookie的SameSite属性,即set-cookie指定属性,当一个来自外部的请求时不能携带cookie。如

Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

  • 1 SameSite的值有两个:
  • Strict - 来自同一站点的所有请求都必须携带cookie,其他任何外部的请求都不能携带cookie。
  • Lax - 当来自同一站点或者顶级导航的请求,且该请求是幂等时携带cookie`,否则其他任何请求不能携带cookie。

这样我们在安全的银行网站浏览发出请求时可以携带JSESSIONID的cookie,而来自外部恶意网站的请求就不能携带此cookie,服务器自然验证后会拒绝该请求。

问题:

  • 一个邮箱链接里跳转到一个社交网站时,不能携带cookie会要求你再次登陆,给用户带来不便。而且一些较老的浏览器可能不支持SameSite属性。因此不建议使用SameSite作为防范CSRF的唯一方式。

一般如果服务提供的请求是给非浏览器端使用的,不建议开启CSRF防御

http网络劫持与DNS劫持原理及预防

原文:blog.csdn.net/weixin_3436…

  • DNS 劫持 DNS服务器都是运营商分配的,所以在这个节点上,运营商可以为所欲为。 例如,访问jiankang.qq.com/index.html, 正常DNS应该返回腾讯的ip,而DNS劫持后,会返回一个运营商的中间服务器ip。访问该服务器会一致性的返回302,让用户浏览器跳转到预处理好的带广告的网页,在该网页中再通过iframe打开用户原来访问的地址。

  • 类似DNS劫持返回302让用户浏览器跳转到另外的地址。(钓鱼网站就是这么干)

  • HTTP劫持 在运营商的路由器节点上,设置协议检测,一旦发现是HTTP请求,而且是html类型请求,则拦截处理。 常见有两种:

    • 在服务器返回的HTML数据中插入js或dom节点(广告)

被劫持怎么办?

  • 对于用户来说,最最直接的就是向运营商投诉。
  • 在html 上加上 <meta http-equiv="Cache-Control" content="no-siteapp"> <meta http-equiv="Cache-Control" content="no-transform " /> 百度官方给的禁止转码声明
  • 最有用的方式,使用HTTPS ,不让数据那么明显的裸奔。 https 加了SSL协议,会对数据进行加密
  • 在开发的网页中加代码过滤,大概思路就是用JavaScript代码检查所有的外链是否属于白名单

各种劫持的手段都有:
1. 直接返回一个带广告的HTML
2. 在原html中插入js,再通过js脚本安插广告;
3. iframe展示原来正常网页。

  • js实际对抗
    原文: www.cnblogs.com/kenkofox/p/… 1、对于DIV注入的,可以初始化时检查全部html代码。 2、对于js注入,可以在window监听DOMNodeInserted事件。 3、对于iframe的情况,要检测非常简单,只需要比较self和top是否相同。

回到ajax

AJAX 最重要的两个对象

  • new XMLHttpRequest() 主流浏览器支持
  • new ActiveXObject('Microsoft.XMLHTTP') Ie6 一下 ie. 永远滴神。

对象上三个重要的方法

open(method, url, flag?) 建立对服务器的调用,

  • 第一个参数 method: get post put**
  • 第二个参数url: 相对url 和绝对url
  • 第三个可选参数 选择异步和同步, 异步是true;

send(content) 向服务器发送请求

setRequestHeader(lable, value) 指定首部, 设置任何首部之前调用open 方法

对象的属性

1、 onreadystatechange 状态改变触发器

2、 readyState 对象的状态(request)

0 代表 未初始化 此时已经创建一个XMLHttpRequest 对象
1 代表读取中,此时代码已经调用XMLHttpRequest的open 方法并且XMLHttprequest已将请求发送出去
2 代表已经读取 此时已经痛过open 方法把一个请求发送到服务端,但是还没有收到
3 代表交互中 此时已经收到http响应头部的信息,但是消息主体部分还没有收到
4 代表完成 此时响应已经被完全接受

3、responseText 服务器进程返回的数据的文本版本 4、responseXML 服务器进程返回的 数据的兼容DOM的XML文档对象 5、status 服务器返回的状态吗(response)

状态码的五种类别

  • 1xx (信息类状态吗),接受的请求正在处理
  • 2xx (成功状态码), 请求接受完毕
  • 3xx (重定向状态码), 需要进行附加操作以完成请求
  • 4xx (客户端错误状态码), 服务器无法请求
  • 5xx (服务端错误状态码), 服务器处理请求出错

常用状态码

状态码参考原文: www.cnblogs.com/TankXiao/ar…

2xx

  • 200 ok 请求已经被服务器正常处理
  • 204 no Content 请求处理成功,但是没有资源可以返回
  • 206 Partial Content 表示客户端进行了范围请求, 而服务器成功执行了这部分的GET 请求, content-Range 在首部里面
3xx
  • 301 Moved Permanently 永久性重定向 请求的URL已移走。Response中应该包含一个Location URL, 说明资源现在所处的位置
  • 302 Found 临时性重定向 类似301 客户端会使用Location中给出的URL,重新发送新的HTTP request
  • 303 SeeOther 类似302
  • 304 Not Modified 客户的缓存资源是最新的, 要客户端使用缓存
  • 307 Temporary Redirect 临时重定向, 和302 的含义相同。

4xx 客户端错误

  • 400 Bad Request 请求报文中存在请求错误
  • 401 Unauthorized 未授权 需要需要通过HTTP 认证的认证信息
  • 403 Forbidden 不允许访问该资源, 请求被服务器拒绝了,可能是token、cookie认证信息没有带
  • 404 服务器没有该资源

5xx 服务器错误

  • 500 Internal Server Error 服务端正在请求时候发生错误
  • 503 Service Unavailable 服务器当机了,暂时处于超负荷或者停机维护,无法处理请求

状态吗大全

1xx

image.png

2xx

image.png

3xx

重定向状态码用来告诉浏览器客户端,它们访问的资源已被移动, Web服务器发送一个重定向状态码和一个可选的Location Header, 告诉客户端新的资源地址在哪。 浏览器客户端会自动用Location中提供的地址,重新发送新的Request。 这个过程对用户来说是透明的。

301和302 非常相似, 一个是永久转移,一个是临时转移。

(SEO中,搜索引擎如果碰到301, 比如网页A用301重定向到网页B,搜索引擎可以肯定网页A永久性改变地址,就会把网页B当做唯一有效目标)

302,303,307 是一样。 这是因为302是HTTP 1.0定义的, HTTP1.1中使用303,307. 同时又保留了302. (但在现实中,我们还是用302,我是没见过303和307) 所以这一节, 我们只需要掌握302, 304 就可以了。

image.png

4xx

image.png

5xx

有时候客户端发送了一条有效Request, Web服务器自身却出错了。 可能是Web服务器运行出错了, 或者网站都挂了

image.png

基于Promise的ajax 封装

低版本IE浏览器的缓存问题

问题:在低版本IE浏览器中,Ajax请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端。后续的请求都会从浏览器的缓存中获取结果,即使服务器的数据更新了,客户端依然拿到的是缓存中的旧数据。 解决:在请求地址后面加上参数,保证每一次请求参数的值不同

xhr.open("GET","http://xxx.xx?t="+new Date().getTime())

onload/onreadystatechange兼容性

image.png

如何中断请求

使用abort()方法可以中止正在进行的异步请求。在使用abort()方法前,应先清除onreadystatechange事件处理函数,因为IE和Mozilla在请求中止后也会激活这个事件处理函数,如果给onreadystatechange属性设置为null,则在IE会发生异常,所以可以为它设置一个空函数

 xhr.onreadystatechange = function(){};
 xhr.abort();

AJAX


function ajax(json) {
  return new Promise((resove, reject) => {
     let {url, method = 'get', flag, data} = json;
     let xhr = null;
     if(window.XMLHttpRequest) {
       xhr = new window.XMLHttpRequest();
     } else {
       xhr = new ActiveXObject('Mircosoft.XMLHTTP');
     }
     const stringParam = getStringParam(data);
     if(method == 'get') {
        url = addQueryToUrl(stringParam, url);
        xhr.open('get', url, flag)
     } else {
        xhr.open('post', url, flag)
     }
     // 不考虑兼容的话 用onload
     xhr.onload = function() {
        const result = {
           status: xhr.status,
           statusText: xhr.statusText,
           headers: xhr.getAllResponseHeader(),
           data: xhr.response || xhr.responseText,
        }
        if((xhr.status >= 200 $$ xhr.status < 300) || xhr.status === 304) {
           resolve(result)
        } else {
          reject(result);
        }
     }
     xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) {
         const result = {
           status: xhr.status,
           statusText: xhr.statusText,
           headers: xhr.getAllResponseHeader(),
           data: xhr.response || xhr.responseText
        }
           resolve(result)
        }
     }
     xhr.onerror = function() {
       reject(nex TypeError('请求出错'))
     }
     xhr.timeout = function() {
       reject(new TypeError('请求超时'))
     }
     xhr.onabort = function() {
       reject(new TypeError('请求被终止'));
     }

     
     // 跨域携带cookie
     xhr.withCredentials = true;
     if(method === 'post') {
       xhr.setRequestHeader("Content-type", "application/x-www-from-urlencoded");
       xhr.send(stringParam);
     }else {
       xhr.send();
     }
     
     
     
  })

}

const addQueryToUrl = function(stringParam, url, isNeedTimestamp) {
  if(!url) return '';
  let _url = url;
  // 有问号提出url并添加至obj,没问号用原url
  if (url.indexOf('?') > -1) {
    _url = url.split('?')[0];
  } 
  let result = '?';
  result += stringParam;
  return `${_url}${result}${isNeedTimestamp ? `&t=new Date().getTime() : ''}`
}

const getStringParam = (param) {
  if(!param || !Object.keys(param).length) return '';
  let dataString = '';
  for (const key in param) {
     if(param[key] !== undefined && param[key] !== null) {
       dataString += `${key}=${param[key]}&`;
     }
  }
  // 去除最后一个& 并返回
  return dataString.slice(0, -1);
}