小白学习笔记之跨域问题

229 阅读2分钟

一.为什么浏览器禁止跨域?

广义跨域就是指跨域访问,比如 A 网站的 js 代码试图访问 B 网站,包括提交内容和获取内 容。它是由浏览器的同源策略造成的,所谓同源是指,域名,协议,端口均相同。不符合同源策 略的,都属于跨域。浏览器出于安全策略考虑,是禁止跨域访问的。因为,跨域访问,会给网络 安全带来隐患。比如scrf攻击。 以DVWA为例。在DVWA的low security模式下,选择scrf.

如图所示,这是该网站修改用户密码的地方。 其服务器端代码如下

<?php 

if( isset( $_GET[ 'Change' ] ) ) { 
    // Get input 
    $pass_new  = $_GET[ 'password_new' ]; 
    $pass_conf = $_GET[ 'password_conf' ]; 

    // Do the passwords match? 
    if( $pass_new == $pass_conf ) { 
        // They do! 
        $pass_new = mysql_real_escape_string( $pass_new ); 
        $pass_new = md5( $pass_new ); 

        // Update the database 
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; 
        $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); 

        // Feedback for the user 
        echo "<pre>Password Changed.</pre>"; 
    } 
    else { 
        // Issue with passwords matching 
        echo "<pre>Passwords did not match.</pre>"; 
    } 

    mysql_close(); 
} 

?>

那么,如果该测试网站用户做出如下行为:

1.用户小花访问'localhost/dvwa/login.php'网站A,并且输入了用户名和密码进行登录。

2.小花在同一tab下打开了另一个危险网址B。该网址html如下:

<img src="localhost/dvwa/vulnerabilities/csrf/?password_new=ccc123
&password_conf=ccc123&Change=Change#"border="0" style="display:none;"/>


<h1>404<h1>

<h2>file not found.<h2>

3.用户如果点击该危险网址B,B会以get方式向A发送请求提交数据。

由于用户小花已登录网站A,网站A已验证用户小花的信息,并已给浏览器返回了cookie信息,那么在cookie会话未结束之前,同一个tab下的危险网站B被用户小花点开,并向网站A发送请求时,cookie信息已经自动添加在请求头里了,而网站A没有区分出这个请求到底是用户小花发出的,还是危险网站B发出的,对这个请求做出了处理。

所以,虽然点击了这个危险网址B的用户小花在网页上只看到显示了404错误,可其实小花在网站A里的密码已被成功更改。而更改者就可以盗用用户的身份向该网址发起请求。而小花如果再重新登陆自己账户后就会发现“login filed”。:

二.跨域问题如何解决?

禁止跨域是针对那些不法网站的,但是也不能因噎废食,让安全的合法网站也没办法跨域访问。 尤其是在前后端分离的情况下,可能前端有一个域名,后端有另一个域名,那么如果不能跨域访 问,前后端将不能实现数据交互。解决跨域的方法不止一种,本文选取其中一种方法----封装 一个简单的jsonp请求。

封装jsonp请求

浏览器端
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
  function jsonp(url, options) {
    const { timeout } = options;
    return new Promise((resolve, reject) => {
      let funcName = `jsonp${Date.now()}`;
      let t =  null, scriptNode;
    //   timeout是为了限制请求时间,如下文,如果请求发送三秒还未返回,则设置为reject状态,报超时,网络错误
    //   如果请求按时回来了,那么就清除这个定时器
      // callback是为了告诉后端,前端定义的接收后端数据的函数名称是什么,如果不告诉,而直接在后端写前端这个名字的话,那么前端改名了后端也要改,这样定义,就只用改前端的
      window[funcName] = function(data) {
        if (t) clearTimeout(t);
        resolve(data);
        // 在函数中调用resolve方法时,proMise的状态就变为fullfilled,即操作成功状态,然后执行封装后调用jsonp中then方法里的操作
        // then方法里有两个参数,onfulfilled(promise为fulfilled状态时执行),onrejected(promise为rejected状态时执行)
        delete window[funcName];  
        document.body.removeChild(scriptNode);   
      }
      scriptNode = document.createElement('script');
      <!-- 
     在网页上添加scr节点,src不受跨域限制,浏览器会将scr里的这段字符串当做js执行,发出请求
     -->
      scriptNode.src = `${url}?callback=${funcName}`;
      document.body.appendChild(scriptNode);
      t = setTimeout(() => {
        reject('network err, timeout')
        // 在函数中调用reject,promise状态变为fullfilled,和then的第二个参数
        //onrejected一样,catch会在promise状态为reject时执行
      }, timeout)
      scriptNode.onerror = function(err) {
        reject(err);
      }
    })
  }
  // promise: 
  // pending resolve
  // pending 
  //调用封装好的jsonp库
  jsonp('http://localhost:9090/api', {
    timeout: 3000
  }).then(res => {
    console.log('jsonp->', res);
  })
  .catch(err => {
    console.log(err);
  })
</script>
</body>
</html>
服务器端
const http = require('http');
// node url模块 解析url
const url = require('url')
http.createServer((req,res)=>{
    if(req.url.includes ('/api')){
        let posts = ['js','php'];
        // res.end(JSON.stringify(posts));
        console.log(req);
        let myurl = url.parse(req.url);
        // console.log(myurl);
        let params = new URLSearchParams(myurl.query);
        let methodName = params.get('callback');
        res.end(`${methodName}(${JSON.stringify(posts)})`)
        // 模板字符串里放 前端接收返回数据的函数名  +   后端要给前端返回的数据
        则返回给前端后,将会当成一段js执行,调用同名函数显示后端返还的数据。
    }
})
.listen(9090,()=>{
    console.log(9090);
})