浅谈一次登录注册与cookie
2018-02-13 本文已影响0人
mayufo
需求
我希望用户能够注册 ->注册后跳转到登录页面 -> 登录页面跳转以后跳转到首页显示我的登录密码
完成效果
代码地址 https://github.com/mayufo/jquery-demo/tree/master/cookies
运行 node server 7000
注册页面
- 前端页面
<div class="form-wrapper">
<h1>注册</h1>
<form id="signUpForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<label>确认密码</label>
<input type="password" name="password_confirmation">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="注册">
</div>
</form>
</div>
- 后端页面返回index
server.js用来模拟后端的返回
if(path === '/'){
let string = fs.readFileSync('./index.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}
- 前端注册逻辑
包装数据: 拿到input框中用户输入的所有数据,存在hash这个对象中
let $signUp = $('#signUpForm')
$signUp.on('submit', (e) => {
let hash = {}
let need = ['email', 'password', 'password_confirmation'] // 需要包装成对象的数据
need.forEach((name) => {
hash[name] = $signUp.find(`[name=${name}]`).val() // 用户必填数据包装到hash中
})
}
需要校验
- 邮箱、密码和确认密码的不能为空
- 密码和确认密码匹配
每次校验前,先清空上次校验的内容
$signUp.find('.error').each((index, span) => {
$(span).text('')
})
校验
if (hash['email'] === '') {
$signUp.find('[name="email"]').siblings('.error').text('填邮箱啊')
} else if (hash['password'] === '') {
$signUp.find('[name="password"]').siblings('.error').text('填密码啊')
} else if (hash['password_confirmation'] === '') {
$signUp.find('[name="password_confirmation"]').siblings('.error').text('填确认密码啊')
} else if (hash['password'] !== hash['password_confirmation']) {
$signUp.find('[name="password_confirmation"]').siblings('.error').text('密码不匹配')
}
- 前端通过校验后发请求
如果以上校验都能通过,就发post 请求,请求成果去到登录页面,如果没有成功,报错
$.post('/signUp', hash)
.then((response) => {
window.location.href= '/signIn' // 请求成功,跳转到登录页面
}, (request) => {
alert('邮箱与密码不匹配')
})
- 后端拿到前端的注册信息, 一段一段,需要封装方法,等全部拿完,再进行操作
如果前端给的注册信息很多,那么它的传输是一段一段的,需要封装一下,当所有的数据都拿到以后,进行存储
// 封装拿数据
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)
})
})
}
- 注册的后端逻辑
if (path === '/signUp' && method === 'POST') {
// 注册
readBody(request).then((body) => {
// body拿到的数据 email=1w%401&password=1&password_confirmation=1
let string = body.split('&') // 以&为例拆分为数组, 每个string ['email=1w%401', 'password=1', 'password_confirmation=1']
let hash = {}
string.forEach((string) => {
let parse = string.split('=') // 以等号为例拆分为数组, 分别取key和value
let key = parse[0]
let value = parse[1]
// 可以看到body的@符号已经编码成了%40,需要解码
hash[key] = decodeURIComponent(value)
})
let {email, password, password_confirmation} = hash
if (email.indexOf('@') === -1) { // 存储的时候判断email的合法性
response.statusCode = 400
// response.write('email is bad ')
response.write(`{
"email": "invalid"
}`)
} else if (password !== password_confirmation ) { // 判断两次密码输入时候一致
response.statusCode = 400
response.write('password not match')
} else {
var users = fs.readFileSync('./user.json', 'utf8') // 读取存储数据的文件
try {
users = JSON.parse(users) // 第一次拿出来的时候是[object Object]
} catch (exception) {
users = [] // 如果没有拿到值,默认为[]
}
let isUse = false // 判断密码是否被注册过
for (let i = 0; i < users.length; i++) {
let user = users[i]
if(user.email === email) {
isUse = true
break
}
}
// 如果密码已经注册过了
if (isUse) {
response.statusCode = 400
response.write('Email is User')
} else {
// 将数据存到刚刚从文件拿出的数组中,并在此存入文件中
users.push({email: email, password: password, password_confirmation: password_confirmation})
fs.writeFileSync('./user.json', JSON.stringify(users))
response.statusCode = 200
}
}
response.end()
})
}
- 通过验证后,后端返回200,前端跳转到登录页面
登录页面
- 后端渲染登录页面
if (path === '/signIn' && method === 'GET') {
let string = fs.readFileSync('./signIn.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}
- 登录页面样式
<div class="form-wrapper">
<h1>登录</h1>
<form id="signInForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="登录">
</div>
</form>
</div>
- 当用户点击登录后
let $signIn = $('#signInForm')
$signIn.on('submit', (e) => {
e.preventDefault()
let hash = {}
let need = ['email', 'password']
// 将登录的数据包装成对象
need.forEach((name) => {
hash[name] = $signIn.find(`[name=${name}]`).val()
})
// 每次请求前,先清除上次的错误提示
$signIn.find('.error').each((index, span) => {
$(span).text('')
})
// 校验输入项
if (hash.email === '') {
$signIn.find('[name="email"]').siblings('.error').text('填邮箱啊')
} else if (hash['password'] === '') {
$signIn.find('[name="password"]').siblings('.error').text('填密码啊')
} else {
// 发送请求
$.post('/signUp', hash)
.then((response) => {
window.location.href= '/enter' // 请求成功进入enter页面
}, (request) => {
// 如果后端返回json数据,并且后端设置了请求头response.setHeader('Content-Type', 'application/json;charset=utf-8')
let {error} = request.responseJSON
if (error.email && error.email === 'invalid') {
console.log('你的邮箱输入错误')
$signIn.find('[name="email"]').siblings('.error').text('邮箱格式错误')
}
})
}
})
- 登录后后端的处理逻辑
为了区别登录的用户和没有登录的用户,可以当用户登录校验成功以后,给头部写上cookie
, response.setHeader('Set-Cookie', sign_in_email=${email})
以此区分
if (path === '/signIn' && method === 'POST') {
// 登录
readBody(request).then(body => {
let string = body.split('&')
let hash = {}
// 对拿到的数据解析为对象
string.forEach(string => {
let parse = string.split('=')
let key = parse[0]
let value = parse[1]
hash[key] = decodeURIComponent(value)
})
let { email, password } = hash
// 读取存储本地数据的文件
var users = fs.readFileSync('./user.json', 'utf8')
try {
users = JSON.parse(users)
} catch (exception) {
users = [] // 当为空值的时候,附默认值
}
if (hash.email.indexOf('@') === -1) { // 判断输入的email是否合理
response.statusCode = 401
response.setHeader('Content-Type', 'application/json;charset=utf-8')
response.write(`{
"errors": {
"email": "invalid"
}}`)
} else {
let found // 判断是否能找到对应的存储
for (let i = 0; i < users.length; i++) {
if (users[i].email === email && users[i].password === password) {
found = true
}
}
if (found) { // 如果本地数据库没有存储,报错
response.statusCode = 200
response.setHeader('Set-Cookie', `sign_in_email=${email}`) // 当登录成功后,设置cookie
} else {
response.statusCode = 401 // 认证失败
}
}
response.end()
})
}
登录页面后,进入enter页面
- 登录后的页面
- 前端页面
<body>
<h1>你的密码是: __password__</h1>
</body>
- 后端渲染登录的页面
if (path === '/enter') {
// 渲染页面
let string = fs.readFileSync('./enter.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
if (request.headers.cookie) {
// 当存在多个cookie的时候,分割为数组,将其包装成对象
let cookies = request.headers.cookie.split('; ')
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 // 筛选出email
let users = fs.readFileSync('./user.json', 'utf-8')
users = JSON.parse(users)
let foundUser
for (let i = 0; i < users.length; i++) {
if (users[i].email === email) { // 将cookie中的email 和本地的email进行对比,如果存在,标志founUser, 并替换__password__为密码
foundUser = users[i]
break
}
}
if (foundUser) {
string = string.replace('__password__', foundUser.password)
} else {
string = string.replace('__password__', '不知道')
}
response.write(string)
response.end()
}
}
cookie 总结
-
设置cookie, 所有同源的请求都会带cookie
-
当服务器收到HTTP请求时,服务器可以在响应头里面添加一个cookie
Set-Cookie: <cookie名>=<cookie值>
-
和关闭浏览器便失效的会话期Cookie不同,持久性Cookie可以指定一个特定的过期时间
-
安全的
Cookie
只应通过HTTPS
协议加密过的请求发送给服务端。即便设置了Secure
标记,只能使用https
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date> // cookie 的最长有效时间
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit> // 在 cookie 失效之前需要经过的秒数
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value> // 指定 cookie 可以送达的主机名。针对的域名,一个网站只会带上自己域名的cookies
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure // 必须使用https 才能访问cookies
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly // js不能访问cookies,但是用户可以手动改,但是js不能读取到
document.cookie // 读取cookie