7-8 前后分离(动态服务器、Cookie、session)

动态服务器

1.静态服务器 V.S. 动态服务器

  • 也叫做:静态网页 V.S. 动态网页

1.1 判断依据

  • 是否请求了数据库
    没有请求数据库,就是静态服务器
    请求了数据库,就是动态服务器
  • 数据库
    1.数据库不属于前端范围,但程序员应该懂一点数据库
    2.直接用json文件当作数据库

1.2 /db/users.json(简化数据库)

  • 基于静态服务器的文件创建一个db目录
  • db:data base 数据库
    创建一个user.json
  • 读取数据库,test.js文件中第一行
    const fs = require('fs')
    require()不是标准JavaScript API的一部分。但是在Node.js中,它是一个内置函数,具有特殊目的:加载模块。
  • 结构:一个数组
[{"id":1,"name":"lzy","password":"xxx"},
{"id":2,"name":"frank","password":"yyy"}]
  • 读取 users 数据
    1.先fs.readFileSync('./db/users.json').toString()
    2.然后JSON.parse一下(反序列化),得到对象
  • 存储 users 数据
    1.先JSON.stringify一下(序列化),得到数组
    2.然后fs.writeFileSync('./db/users.json',data)

1.3 读/写数据库

const fs = require('fs')

//读数据库
const usersString = fs.readFileSync('./db/users.json').toString()
const usersArray = JSON.parse(usersString)//变成对象

//写数据库
const user3 = {id:3,name:'tom',password:'zzz'}
usersArray.push(user3)
const string = JSON.stringify(usersArray)//变数组
fs.writeFileSync('./db/users.json',string)//写文件

2.目标1

  • 实现用户注册功能
    1.用户提交用户名和密码
    2.users.json 里就新增一行数据

2.1 思路

  • 前端写一个 form(表单),让用户填写 name 和 password
  • 前端监听 submit 事件
    回车或者鼠标点击
  • 前端发送 post 请求,数据位于请求体
    发送请求到服务器
  • 后端接收 post 请求
  • 后端获取请求体中的 name 和 password
  • 后端存储数据

2.2 发送请求

  1. 数据库要清空,users.json中只留下一个空数组(不能什么都没有)
  2. 先新建一个新页面register.html(记得所有页面都要在手机上运行),做个表单,表单里面是3个div,2个div里面是用户名和密码的label,把input写在label里面,另一个是登陆的botton(注意:button的属性要是submit)
  3. 要先引入一个jQuery(BootCDN),点击注册之后,要监听表单的submit事件,获取事件
  4. 第一步是阻止默认事件,然后再写自己的代码
  5. 获取到用户填写的用户名和密码:在$form表单里面找(find)name=name的input,获取它的值,password同理。
  6. 用jQuery发送一个AJAX请求,POST请求,新建一个 /registe 的url,data是得到的用户名和密码(记得变成数组)
    上传的是一个JSON字符串,要告诉浏览器Content-Type
//users.json
[]

//register.html 注册页面
//上传一个 json,服务器你最好获取到
<form id="registerForm">
    <div>
      <label>用户名<input type="text" name="name"></label>
    </div>
    <div>
      <label>密码<input type="password" name="password"></label>
    </div>
    <div>
      <button type="submit">注册</button>
    </div>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"</script>
<script>
    const $form = $('#registerForm')
    $form.on('submit',(e)=>{
      e.preventDefault()
      const name = $form.find('input[name=name]').val()
      const password = $form.find('input[name=password]').val()
      console.log(name,password)
    })
    //jQuery的ajax发送POST请求
    $.ajax({
      method:'POST',
      url:'/register',
      contentType:'text/json;charset=UTF-8',
      data:JSON.stringify({name,password})
    })
</script>

2.3 接收请求

  • 发送了一个请求去 /register,但它不对应任何一个文件,因此要在server.js中添加一个路由
  • 判断路径是不是 /register,method是不是post
  • get请求可以通过query拿到,post请求通过request
  • 首先声明一个数组,然后往数组里面塞数据(因为数据有可能是分段上传的)
  • 监听request(请求)的data(上传)事件,获取到上传的数据,然后把数据push到array里
  • 监听一个end事件,结束的时候把数组打出来
  • 打出来的是UTF-8编码的数据,Buffer提供了一个把不同的字符合成一个字符串的功能
  • 这样就可以获取到字符串,然后把字符串变成一个对象,服务器就能获取到对象的name和password
  • 声明一个新的user,读一下数据库看看新的user的ID应该是第几个,如果存在数据就最大的ID+1,如果没有数据就取ID=1
  • 然后就可以存入文件
//server.js 获取到上传的数据
if (path === "/register" && method === "POST") {
    response.setHeader("Content-Type", "text/html; charset=utf-8");
    const userArray = JSON.parse(fs.readFileSync("./db/users.json"));
    const array = [];
    request.on("data", chunk => {
      array.push(chunk);
    });
    request.on("end", () => {
      const string = Buffer.concat(array).toString();
      const obj = JSON.parse(string);
      const lastUser = userArray[userArray.length - 1];
      const newUser = {
        // id 为最后一个用户的 id + 1
        id: lastUser ? lastUser.id + 1 : 1,
        name: obj.name,
        password: obj.password
      };
      userArray.push(newUser);
      fs.writeFileSync("./db/users.json", JSON.stringify(userArray));
      response.end()
    });
}

2.4 注册之后

  • 注册成功之后,ajax 的 then 函数,成功则提示注册(alert)成功,跳转(location.href)到登陆页(新的HTML文件)
//register.html
$.ajax({
      method:'POST',
      url:'/register',
      contentType:'text/json;charset=UTF-8',
      data:JSON.stringify({name,password})
    }).then(()=>{
       alert('注册成功!')
       location.href = '/sign_in.html'
    },()=>{})
 // sign_in.html
 登陆成功页面!

3.目标2

3.1 实现用户登陆功能

  • 首页 home.html,已登陆用户可看到自己的用户名
  • 登陆页 sign_in.html,供提交用户名和密码
  • 输入的用户名和密码如果是匹配的,就自动跳转首页

3.2 sign_in.html 思路

  • 前端写一个 form,让用户填写 name 和password
  • 前端监听submit事件
  • 前端发送post请求,数据位于请求体
//sign_in.html 登录页
//和 注册页面 差不太多:都是表单,输入用户名和密码,发送post请求,成功转跳首页
<form id="signInForm">
    <div>
      <label>用户名 <input type="text" name="name"></label>
    </div>
    <div>
      <label>密码 <input type="password" name="password"></label>
    </div>
    <div>
      <button type="submit">登录</button>
    </div>
  </form> 
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script>
    const $form = $('#signInForm')
    $form.on('submit', (e)=>{
      e.preventDefault()
      const name = $form.find('input[name=name]').val()
      const password = $form.find('input[name=password]').val()
      $.ajax({
        method: 'POST',
        url: '/sign_in',
        contentType: 'text/json; charset=UTF-8',
        data: JSON.stringify({name, password})
      }).then(()=>{
        alert('登录成功')
        location.href = '/home.html' //转跳首页
      }, ()=>{})
    })
</script>
  • 后端接收post请求
  • 后端获取请求体中的 name和password
  • 后端读取数据,看是否有匹配的 name 和 password
  • 如果匹配,后端应标记用户已经登陆
//server.js  匹配传上来的name和password
if (path === "/sign_in" && method === "POST") {
    const userArray = JSON.parse(fs.readFileSync("./db/users.json"));
    const array = [];
    request.on("data", chunk => {
      array.push(chunk);
    });
    request.on("end", () => {
      const string = Buffer.concat(array).toString();
      const obj = JSON.parse(string); // name password
      const user = userArray.find(
        user => user.name === obj.name && user.password === obj.password
      );
      if (user === undefined) {
        response.statusCode = 400;
        response.setHeader("Content-Type", "text/json; charset=utf-8");
        response.end(`{"errorCode":4001}`)//公司自定义的错误码
      } else {
        response.statusCode = 200;
        response.end()
      }
    });
  } 

3.Cookie

  • home.html
    问题:如何区别从 登陆页 跳转的用户和直接输入网址进入该页面的用户
    解决:使用 Cookie!
    使用 Cookie 就能知道用户是否登陆了页面

3.1 定义

  • Cookie是服务器下发给浏览器的一段字符串
  • 浏览器必须保存这个Cookie(除非用户删除)
  • 之后发起相同二级域名请求(任何请求)时,浏览器必须附上Cookie
    二级域名:CSS、JS等

3.2 举例

  • 假如你是公园检票员,你怎么知道谁能进谁不能进?
  • 有票就能进,没票不能进
  • Cookie就是门票,用户登陆后用户名和密码匹配后,就发一张门票
    由后端发门票
  • 有Cookie就是登录后跳转的用户,没有Cookie就是没登陆过的用户

3.3 Set-Cookie响应头

  • 从服务器设置给浏览器
    前端最好不要设置Cookie
  • response.setHeader('Set-Cookie','logined=1;HttpOnly')
    1.Cookie可以设置过期时间
    2.HttpOnly:不准前端访问Cookie
    ——访问:document.cookie
    ——可以访问就可以修改:document.cookie=‘logined=0’
    ——加上HttpOnly之后,就没办法读到
  • 浏览器会保存这张票,在Application中的Cookies中
    1.name:logined
    2.value:1
//server.js  匹配传上来的name和password
if (path === "/sign_in" && method === "POST") {
    //读取用户信息文件;获取 POST 的数据;进行用户信息匹配
      if (user === undefined) {
      //未匹配用户信息
      } else {
        response.statusCode = 200;
        response.setHeader("Set-Cookie",'logined=1;HttpOnly')
        response.end()
      }
    });
  } 

在这里插入图片描述

3.4 home页面

  • 页面登陆过会有Cookie,在请求 home页面的时候Cookie会自动被请求带到home中
  • Home页面读取Cookie
    1.如果登陆过,Cookie就是:logined=1
    2.如果没有登陆过,Cookie就是:undefined
  • 判断如果Cookie等于'logined=1'(Cookie就是字符串)
    如果是,就替换掉home页面中的占位符为已登陆,否则就是未登录
//server.js
if (path === "/home.html") {
    const cookie = request.headers["cookie"]; //打出来是:logined=1
    if(cookie === "logined=1"){
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      const string = homeHtml.replace('{{loginStatus}}','已登录')
      response.write(string)
    }else{
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      const string = homeHtml.replace('{{loginStatus}}','未登录')
      response.write(string)
    }
}

3.5 home.html 显示登陆的是谁

  • home.html 怎么知道登陆的是谁?
    把Cookie中的 logined=1 改成 user_id
    user_id=${user.id}
  • 这样的话,读到Cookie,home就知道登陆的是谁了
    1.获取到Cookie
    2.通过层层筛选切割,获取到id数字
  • home.html渲染前获取user信息
    如果有 userId,说明已经登陆,则将{{user.name}}替换成user.name
    如果无 user,则显示登陆按钮
//server.js
if (path === "/home.html") {
    const cookie = request.headers["cookie"]; //打出来是:logined=1
    let userId
    try{
      userId = cookie.split(';').filter(s=>s.indexOf('user_id=')>=0)[0].split('=')[1]
    }catch(error){} //出现错误就什么都不做
    if(userId){
      const userArray = JSON.parse(fs.readFileSync("./db/users.json"))
      const user = userArray.find(user=>user.id.toString()===userId)
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      let string
      if(user){
        string = homeHtml.replace('{{loginStatus}}','已登录')
           .replace('{{user.name}}',user.name)
      }else{} //没有就什么都不做
      response.write(string)
    }else{
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      const string = homeHtml.replace('{{loginStatus}}','未登录')
           .replace('{{user.name}}','')
      response.write(string)
    }
}

3.6 有一个大bug

  • 用户可以篡改 user_id
    1.当用户登录之后可以在application中看到Cookie,用户就可以修改value
    2.如果知道了其他人的Cookie value 就可以改成别人的,这样就能跳转到别人的页面
  • 需要防止篡改 user_id
  • 方法一:加密
    1.将user_id加密发送给前段,后端读取 user_id 时解密,此法可行,但有安全漏洞
    2.漏洞:加密后内容可无限期使用(如果在机场登陆WiFi被人窃听了加密的user_id,那么那个人就可以使用这个加密的user_id登陆页面)
    3.解决方法:JWT
  • 方法二:把信息隐藏在服务器
    也就是session

4.session 会话

  • 把信息隐藏在服务器里
    1.把用户信息放在服务器 x 里,再给信息一个随机id
    2.把随机id发给服务器
    3.后端下次想读取到id是,通过 x[id] 获取用户信息
  • 特点
    1.此法用户为什么无法篡改id?
    ——答:因为id很长,而且随机,根本没法猜到别的用户的ID
    2.x 是什么?
    ——答:是文件,不能用内存,因为断电就清空
    3.这个 x 又被叫做 session(会话)
    4.为什么叫做会话?
    ——当你登陆成功,服务器就开始了服务,这就很像服务器和浏览器之间的会话

1.session.json

//session.json 表驱动编程
{
  //"随机数":"user_id"
  "12837982247893682":{"user_id":2}
}
  • 如果我给你浏览器12837982247893682,那代表我给的 user_id 是 2
  • 如何给 session
    1.session.json清空
    2.使用随机数传递Cookie
//session.json
{
}

//server.js
if (path === "/sign_in" && method === "POST") {
    //读取用户信息文件;获取 POST 的数据;进行用户信息匹配      
    if (user === undefined) {
      //未匹配用户信息
    } else {
      response.statusCode = 200;
      const random = Math.random()
      const session = JSON.parse(fs.readFileSync('./session.json').toString())
      session[random] = {user_id:user.id}
      fs.writeFileSync('./session.json',JSON.stringfy(session))
      response.setHeader("Set-Cookie",`session_id=${random};HttpOnly`)
      response.end()
    }
  });
} 
  • 请求 sign_in 文件后,在 Response Header 里面的
  • home页面就不获取userId,就获取 session id
//server.js
if (path === "/home.html") {
    const cookie = request.headers["cookie"]; //打出来是:logined=1
    let sessionId
    try{
      sessionId= cookie.split(';').filter(s=>s.indexOf('session_id=')>=0)[0].split('=')[1]
    }catch(error){} //出现错误就什么都不做
    if(sessionId && session[sessionId]){
      const session = JSON.parse(fs.readFileSync("./session.json").toString())
      const userId = session[sessionId].user_id
      const userArray = JSON.parse(fs.readFileSync("./db/users.json").toString())
      const user = userArray.find(user=>user.id===userId)
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      let string = ''
      if(user){
        string = homeHtml.replace('{{loginStatus}}','已登录')
           .replace('{{user.name}}',user.name)
      }else{} //没有就什么都不做
      response.write(string)
    }else{
      const homeHtml = fs.readFileSync("./public/home.html").toString()
      const string = homeHtml.replace('{{loginStatus}}','未登录')
           .replace('{{user.name}}','')
      response.write(string)
    }
}
  • session的时效性
    1.session可以定时注销
    2.每次登陆可以创建一个新的ID
    3.如果怀疑自己泄露就重新登陆即可
    4.使用session,信息是在服务器这边的 session.json 文件里面,我们可以随时修改,而加密信息是在用户那边,加密是不能删除的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值