Cookie与登录注册

首先,写一个简单的注册页面sign_up.html(前端)

    

注册

把用户输入的内容放到一个哈希中(前端)

        let $form = $('#signUpForm')
        let hash = {}
        $form.on('submit', (e) => {
            e.preventDefault()
            let need = ['email', 'password', 'password_confirmation']
            need.forEach( (name) => {
                let value = $form.find(`[name = ${name}]`).val()
                hash[name] = value
            })
        })  //得到的hash为 {email: "xx", password: "xx", password_confirmation: "xx"}

给server.js里添加一个路由,如果请求路径是/sign_up,就显示当前目录下的sign_up.html文件(后端)

 if(path === '/sign_up'){
    let string = fs.readFileSync('./sign_up.html', 'utf8')
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
 }

node server 8080 打开server,访问http://localhost:8080/sign_up就可以看到我们写的注册页面。
尝试发送一个Ajax post请求:jQuery.post() (前端)

$.post('/sign_up', hash)
      .then( (response) => {
          console.log(response) //得到一串符合html格式的字符串
      }, () => {
          console.log(‘error’)
      })

打印出来一个html格式的字符串,因为server.js中写明了只要请求路径是/sign_up就表示请求成功,返回字符串。

由于这是一个post请求,如果在路由里将请求方式的限制为get,则会打印出‘error’:(后端)

 if(path === '/sign_up' && method === 'GET'){
    let string = fs.readFileSync('./sign_up.html', 'utf8')
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
 }

所以要再添加一个请求方式为post的路由:(后端)

 if(path === '/sign_up' && method === 'POST'){
    readBody(request).then( (body) => {
        response.statusCode = 200
        response.end()
    })    
 } //用户post到/sign_up, 服务器读取到请求的第四部分(Form Data),得到字符串'email=1&password=2&password_confirmation=3'

//由于请求的第四部分(Form Data)是分段上传的,Node.js无法直接读到其内容,封装以下函数:
//作用是获取请求第四部分数据,并返回一个Promise对象
function readBody(request){
  return new Promise((resolve, reject)=>{
    let body = []
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString();
      resolve(body)
    })
  })
}

现在用户向sign_up发送post请求,服务器得到的请求的第四部分是一个字符串'email=1&password=2&password_confirmation=3',使用split()方法将其变成哈希:(后端)

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

以上所做的事情总结起来就是:
客户端收集form表单内容到一个hash中,作为一个字符串传给服务端,
服务端以一个字符串的形式拿到表单内容,
再将其还原为一个hash。

这就是前端向后台传数据的方式:前端把所有东西变成一个字符串传给后台,后台从字符串里按照所要的结构解析出来。

现在服务器有了一个包含表单信息的hash,继续进行验证:(后台)

let {email, password, password_confirmation} = hash
if(email.indexOf('@') === -1){ //验证用户输入的邮箱中是否有@
    response.statusCode = 400
    response.setHeader('Content-Type', 'application/json;charset=utf-8') //jQuery只要发现响应中说了这是json就会自动JSON.parse()
    response.write(`{
        "errors": {
            "email": "invalid"
        }
    }`)
}else if(password !== password_confirmation){ //验证两次输入的密码是否匹配
    response.statusCode = 400
    response.write('password not match')
}else{
    response.statusCode = 200
}

(前端)

$.post('/sign_up', hash)
                .then( (response) => {
                    console.log(response)
                }, (request) => {
                    let {errors} = request.responseJSON
                    if(errors.email && errors.email === 'invalid'){
                        $form.find('[name = "email"]').siblings('.error')
                            .text('邮箱格式错误')
                    }
                })

前端也可以在发起请求之前进行一些验证:(前端)jQuery.each()

            $form.find('.error').each( (index, span) => {
                $(span).text('')
            })
            if(hash['email'] === ''){
                $form.find('[name = "email"]').siblings('.error')
                    .text('填邮箱呀同学')
                return
            }
            if(hash['password'] === ''){
                $form.find('[name = "password"]').siblings('.error')
                    .text('设密码呀同学')
                return
            }
            if(hash['password_confirmation'] === ''){
                $form.find('[name = "password_confirmation"]').siblings('.error')
                    .text('验密码呀同学')
                return
            }
            if(hash['password'] !== hash['password_confirmation']){
                $form.find('[name = "password_confirmation"]').siblings('.error')
                    .text('密码不匹配')
                return
            }

现在,我们实现的功能是邮箱、密码、验证密码必填,密码与验证密码相同(前端验证),邮箱中必须有'@'(后端验证)。
由于前后端代码都是JS,所以后端的验证前端都能做到。但是前端可以不验,后端必须要验。例如用crul发请求的话,可以直接与服务器交流。所以不能依赖浏览器上的JS,必须确保后端是安全的。

验证成功之后,服务器需要将得到的信息保存到数据库中。这里我们在当前目录下创建db/users文件作为数据库,初始化为一个空数组[]。(后端)

var users = fs.readFileSync('./db/users', 'utf8')
try{
  users = JSON.parse(users) // 如果users不符合JSON语法就放弃它,把它变成空数组
}catch(exception){
  users = []
}
users.push({email: email, password: password})
var usersString = JSON.stringify(users) //对象不能直接存,将其变成字符串
fs.writeFileSync('./db/users', usersString)
response.statusCode = 200

现在有个问题,同一个邮箱可以多次注册。服务器可以在将信息存入数据库之前验证邮箱是否已注册:(后端)

var users = fs.readFileSync('./db/users', 'utf8')
try {
    users = JSON.parse(users) // 如果users不符合JSON语法就放弃它,把它变成空数组
} catch (exception) {
    users = []
}
let inUse = false
for(let i=0; i

至此注册过程完成。接着来做登录功能。
首先写一个登录页面sign_in.html:(前端)


    

登录

在服务器上给sign_in.html写一个路由:(后端)

    else if (path === '/sign_in' && method === 'GET') {
        let string = fs.readFileSync('./sign_in.html', 'utf8')
        response.statusCode = 200
        response.setHeader('Content-Type', 'text/html;charset=utf-8')
        response.write(string)
        response.end()
    } else if (path === '/sign_in' && method === 'POST') {
        readBody(request).then((body) => {
            let strings = body.split('&') // ['email=1', 'password=2']
            let hash = {}
            strings.forEach((string) => {
                let parts = string.split('=') // ['email', '1']
                let key = parts[0]
                let value = parts[1]
                hash[key] = decodeURIComponent(value) // hash['email'] = '1'
            })
            
            response.end()
        })
    } 

登录页面与注册页面相似,客户端将表单信息收集到一个哈希中,在发起请求时传一个字符串给后端;后端将得到的字符串解析为哈希。

后端得到包含email和password的哈希后,与数据库进行对比(看数据库里有没有一样的email和password),认证用户:(后端)

            let { email, password } = hash
            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.statusCode = 200
            } else {
                response.statusCode = 401
            }

登录成功时跳转到首页:(前端)

$.post('/sign_in', hash)
                .then( (response) => {
                    window.location.href = '/'
                }, (request) => {
                    alert('邮箱与密码不匹配')
                })

现在有一个问题:即使没有登录,用户也可以直接访问首页,与登录后看到的页面相同。
这里我们需要用到Cookies。HTTP Set-Cookie
服务器在认证用户成功后,给返回的响应头中添加Set-Cookie:(后端)

if (found) {
    response.setHeader('Set-Cookie', `sign_in_email=${email}`)
    response.statusCode = 200 
} 

选中开发者工具的Preserve log,再次登录。
我们可以看到,在向sign_in发起请求时,得到的响应头中有一项Set-Cookie: [email protected],而请求成功后接着对主页发起的请求的请求头中带着这个Cookie:Cookie: [email protected]

这就是Cookie的作用:服务器响应中给页面发送一个Cookie,之后同源发起的请求都带着这个Cookie作为识别。
理解:第一次进公园时售票员给你两天的票(Set-Cookie),票就是Cookie,两天内可以多次进入公园,每次都要带着票给售票员看。

Cookie特点:

  1. 服务器通过Set-Cookie响应头设置Cookie
  2. 浏览器得到Cookie之后,每次请求都要带上Cookie
  3. 服务器读取Cookie就知道登录用户的信息

问题:

  1. 我在Chrome登录得到了Cookie,用Safari访问,Safari会带上Cookie吗?
    no
  2. Cookie存在哪?
    Window存在C盘的一个文件里,其他系统也都存在硬盘的一个文件里。
  3. Cookie能作假吗?
    可以。Chrome开发者工具Application-Cookies就可以改。所以Cookie是不安全的。HttpOnly可以限制。
  4. Cookie有有效期吗:
    默认有效期20分钟左右,由浏览器决定。后端可以强制设置有效期。

现在我们让登录的用户在跳转到首页时可以看到自己的密码:(后台)

if (path === '/') {
        let string = fs.readFileSync('./index.html', 'utf8')
        
        //从cookies里拿到用户的email
        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里email匹配的用户信息
        let users = fs.readFileSync('./db/users', 'utf8')
        users = JSON.parse(users)
        let foundUser
        for (let i = 0; i < users.length; i++) {
            if (users[i].email === email) {
                foundUser = users[i]
                break
            }
        }
        
        //如果找到用户信息,将用户的密码显示在页面中
        if (foundUser) {
            string = string.replace('__password__', foundUser.password)
        } else {
            string = string.replace('__password__', '不知道')
        }
        response.statusCode = 200
        response.setHeader('Content-Type', 'text/html;charset=utf-8')
        response.write(string)
        response.end()
    }

index.html:


    

你的密码是:'__password__'

总结一下流程:

  1. 用户打开sign_up注册,向服务器发送post请求,发送email,password,password_confirmation;
  2. 服务器验证通过,把用户信息写进数据库,并告诉用户注册成功;
  3. 用户打开sign_in登录,发送post请求,发送email和password;
  4. 服务器认证通过,Set-Cookie;
  5. 用户带着Cookie打开首页,发送get请求;
  6. 服务器读取Cookie,从Cookie中得到email,根据email从数据库找到匹配的用户信息写入页面;
  7. 这样登录的用户进入首页时可以看到自己的信息;
  8. 没有登录的访客进入首页看到不同的页面。

完整代码

你可能感兴趣的:(Cookie与登录注册)