网页注册登录功能的实现以及Cookie的利用

    原本打算先写一篇关于JSONP的安全问题的博客的,但是由于有部分知识目前还不能清楚地表达出来,因此打算先暂时把JSONP的安全问题的博客先放一放,今天先来讲讲如何实现网页的登录和注册的功能以及Cookie的利用。


    在开始之前呢,我先来给大家捋一捋,一个简单的注册和登录过程中,客户端和服务器端需要经过的过程,方便大家在之后加入代码时候的理解。

    首先是注册过程,注册过程中会经历的步骤和过程有以下几点:
    1.用户输入正确格式的邮箱、用户名和密码并点击注册。
    2.前端页面判断用户输入的信息的格式是否正确和合法。
    3.客户端把这些信息提交至服务器端,服务器获取全部内容提取需要的信息。
    4.服务器端对这些信息进行再次的判断,判断用户输入的信息的格式是否正确和合法,以及与数据库中的已有信息作比较,判断相关信息是否已经被注册。
    5.若判断后发现没有问题,则返回客户端HTTP状态码200表示注册成功,并把新注册用户的信息写入数据库内,若发现问题,则返回HTTP状态码400和对应的错误信息表示注册失败。
    6.客户端收到状态码展示对应的内容。


    而在登录过程中会经历的步骤和过程有以下几点:
    1.用户输入正确格式的用户名和密码并点击登录。
    2.前端页面判断用户输入的信息的格式是否正确和合法。
    3.客户端把这些信息提交至服务器端,服务器获取全部内容并提取需要的信息。
    4.服务器端对这些信息进行再次的判断,判断用户输入的信息的格式是否正确和合法,并与数据库中的已注册账户信息进行核对,看看对应的用户名和密码是否相等。
    5.若核对发现信息相等,则设置Cookie,并返回客户端HTTP状态码200表示注册成功,若核对失败,则返回HTTP状态码401和对应的错误信息表示注册失败。
    6.客户端若收到401状态码,则提示用户登录失败,并展示对应错误信息,若收到200状态码,则带上收到的Cookie对首页的url发起请求
    7.服务器收到请求后解析请求所携带的Cookie,提取出Cookie所包含的用户的信息。
    8.把首页中的部分公共信息更替为该用户自己的个人信息,并把更替好的内容返回给客户端。
    9.客户端向用户展示返回的内容。

    这里值得说明的是,前端可以不判断和验证注册以及登录的内容,但是后端必须要添加验证,因为用户可以通过在命令行使用curl发送请求,这样会跨过浏览器的验证,因此后端的验证是必须要存在的。


    以上就是在一个简单的注册和登录过程中,客户端和服务器端需要经过的过程,接下来我们通过展示代码,一步一步慢慢地来看看是如何实习注册功能和登录功能的,以及Cookie又是如何被应用的。
    客户端的代码在本篇文章中就不展示了,因为无外乎就是新建一个表单,然后添加一些样式,相信读者应该可以轻松知道如何创建在客户端的代码,因此我们重点来讲服务器端的。

注册

    我们先来看注册,与服务器有关的步骤从第三条开始:“3.客户端把这些信息提交至服务器端,服务器获取全部内容并提取需要的信息。”

let body = []
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  console.log(body)
})

    这个打印出来的body变量就是服务器获取到的全部请求体,之所以要这样写,是因为请求体不是全部一次性上传完毕的,而是需要服务器一段一段地获取的,因此需要每次ondata之后就把获取到的请求体并入body中,最后通过body = Buffer.concat(body).toString();就可以获得全部的请求体。
    我们假设获取到的请求体为:email=1&password=2&password_confirmation=3
    之后,我们需要从中提取相关的信息:

let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
let hash = {}
strings.forEach((string)=>{
  // string == 'email=1'
  let parts = string.split('=') // ['email', '1']
  let key = parts[0]
  let value = parts[1]
  hash[key] = decodeURIComponent(value) // hash['email'] = '1'
})
let {email, password, password_confirmation} = hash

    通过以上的操作,我们就从请求体中提取到了email和password的信息了。至此注册的第三步就算完成了,之后第四步的判断的代码如下所示:

if(email.indexOf('@') === -1){
    response.statusCode = 400
    response.setHeader('Content-Type', 'application/json;charset=utf-8')
    response.write(`{
        "errors": {
        "email": "invalid"
        }
    }`)
    }else if(password !== password_confirmation){
    response.statusCode = 400
    response.write(`{
        "errors": {
        "password": "notmatch"
        }
    }`)
    }else{
    var users = fs.readFileSync('./db/users', 'utf8')
    try{
        users = JSON.parse(users) // []
    }catch(exception){
        users = []
    }
    ...
}

    其中,以上代码涉及到一个被称为“前后端撕逼协议”的代码内容:

response.write(`{
    "errors": {
    "email": "invalid"
    }
}`)

    该协议是指后端返回错误信息时应严格按照JSON的数据格式输出,因为若任意地使用字符串来输出错误信息,会出现许多因为格式不统一的问题而导致前后端交互困难,因此有了该协议的诞生。
    另外,用到try和catch的原因是因为防止数据库中的数据不符合格式导致出现错误,因此当运行try里的代码出错时,会直接运行catch里面的代码来清空该数据库来保证之后的步骤能正常运行。

let inUse = false
for(let i=0; i<users.length; i++){
    let user = users[i]
    if(user.email === email){
    inUse = true
    break;
    }
}
if(inUse){
    response.statusCode = 400
    response.write(`{
        "errors": {
        "email": "inuse"
        }
    }`)
}else{
    users.push({email: email, password: password})
    var usersString = JSON.stringify(users)
    fs.writeFileSync('./db/users', usersString)
    response.statusCode = 200
}

    第四部分若注册信息没有被占用,变量inUse会为false,返回200状态码并把注册信息写入数据库内,若失败则为true,之后返回对应的状态码和信息即可。至此,注册部分的代码就讲完了,接下来我们讲一下登录的过程和步骤。



登录

    登录部分的代码与注册部分的有着异曲同工之妙,因为都是先提取重要的信息之后进行一系列的判断,并最终做出对应的反应。
    登录的第三个步骤和注册基本一样,代码也基本一样,读者只要模仿着注册的写法就能把登录部分的代码写出来,因此就不在这里重新写了。
    紧接着就是判断登录信息是否合法,代码如下所示:

var users = fs.readFileSync('./db/users', 'utf8')
try{
  users = JSON.parse(users) // []
}catch(exception){
  users = []
}
let found
for(let i=0;i<users.length; i++){
  if(users[i].email === email && users[i].password === password){
    found = true
    break
  }
}
if(found){
  response.setHeader('Set-Cookie', `sign_in_email=${email}`)
  response.statusCode = 200
}else{
  response.statusCode = 401
}
response.end()

    可以看到,该部分的代码与注册也是十分相似的,不同的地方在于以下几点:
    1.注册和登录的判断和核对的条件不同。
    2.登录成功在返回状态码200的同时,还需要通过设置响应头来设置一个Cookie值以便之后请求相同的源的网址的时候使用。

    登录步骤的第五部分中,若在数据库中找到与用户输入信息相等的用户信息,则变量found会为true,返回200状态码并设置Cookie值,表示登录成功,若失败则为false,并返回HTTP状态码401。

    登录成功后,客户端会发起首页请求,并带上用户对应的Cookie,服务器收到请求后,会第一时间解析这个Cookie,代码如下:

let string = fs.readFileSync('./index.html', 'utf8')
let cookies =  request.headers.cookie.split('; ') // ['email=1@', 'a=1', 'b=2']
let hash = {}
for(let i =0;i<cookies.length; i++){
  let parts = cookies[i].split('=')
  let key = parts[0]
  let value = parts[1]
  hash[key] = value 
}
let email = hash.sign_in_email

    通过以上代码,服务器就能从Cookie中提取出当前的用户信息,之后只要在数据库中找到对应用户的信息并于首页信息相替换,再返回替换后的首页就可以达到登录首页并展示在首页展示个人信息的目的了。

    至此,登录的过程也全部讲完了。

    看到这里可能你会因为过程数量和代码数量过多而有点晕眩,没关系,多看几次就好哈哈。

Cookie

    最后我们再说说Cookie吧,从上面可以稍微地看出,Cookie的作用就是识别用户的身份,Cookie里面含有用户的一些个人信息。其实Cookie还有另外一个作用,就是记录历史,一些购物网站的购物车就是应用了这个作用。

    关于Cookie的定义,我这里选择摘录方应杭老师在知乎上的说法:
    1. Cookie 是浏览器访问服务器后,服务器传给浏览器的一段数据。
    2. 浏览器需要保存这段数据,不得轻易删除。
    3. 此后每次浏览器访问该服务器,都必须带上这段数据。

    而Cookie有着以下的特点:
    1.服务器通过set-Cookie响应头设置Cookie。
    2.浏览器得到Cookie之后,每次请求都要带上Cookie。
    3.服务器读取Cookie就知道登录用户的信息(例如email、用户名等)。

    另外,Cookie是存在有效期的,默认是20分钟左右,之所以说左右,是因为具体时间由浏览器自动决定,但是后端可以强制设置Cookie的有效期,设置方法请读者自行查看Set-Cookie MDN
    最后,Cookie是可以修改的,因此存在着很大的安全隐患,需要通过一些手段来保证它的安全性。还有退出登录的方法就是清除相关的cookies和session,这些内容需要读者自行去搜索,在这里就不再做详细叙述了。


    好了,本篇文章到这里就结束了,边幅有点长,希望对大家有所收获吧~
    要继续研究一下JSONP的安全问题去了,争取这两天内能把文章写出来吧~



本教程版权归宣泽彬所有,转载须说明来源

推荐阅读更多精彩内容