1.登录开始
注册的话其实就像是新增博客,没啥难度,更何况现在都短信注册或者微信扫码等等,没有太大意义。
登录:
- 核心:登录校验&登录信息存储
- 为何只讲登录,不讲注册?
Mysql 是硬盘数据库,redis 是内存数据库。
目录:
- cookie和session
- session写入 redis
- 开发登录功能,和前端联调(用到 nainy后向代THP
2.cookie简介
主要了解什么?
- 什么是cookie
- javascript操作cookie ,浏览器中查看cookie
- server端操作cookie ,实现登录验证
跨域不共享就是说淘宝和百度的 cookie 是不一样的,各自有各自的 cookie。
什么是cookie?
- 存储在浏览器的一段字符串(最大5kb )
- 跨域不共享
- 格式如k1=v1; k2=v2; k3=v3;因此可以存储结构化数据
请求域的 cookie 就是说请求什么如请求百度的 xx 文件就是请求百度的 cookie(不管是在什么页面请求,主要看的是请求的是谁而不是看在什么页面)。
cookie:
- 每次发送http请求,会将请求域的cookie一起发送给server
- server可以修改cookie并返回给浏览器
- 浏览器中也可以通过javascript修改cookie(有限制)
所谓有限制就是服务端返回后将其锁死不愿意让客户端去修改(为了安全)。
客户端JavaScript操作cookie:
- 客户端查看cookie ,三种方式
- javascript查看、修改cookie(有限制)
document.cookie 查看 cookie ,添加的话其实是采用累加的方式 document.cookie = ‘要添加的键值对’(就直接加在 cookie 的后面)。查看 application 下面有 cookie 的详细信息,其中的 path 如果是 / 代表的是根路径,就是说不管我们 url 路径多复杂,都可以使用该 cookie 。
3.cookie用于登录验证
server端Nodejs操作cookie:
- 查看cookie
- 修改cookie
- 实现登录验证
回到 app.js,我们来解析 cookie,查看请求的 headers 就可以拿到 cookie,没有就返回空字符串,需要注意的是由于返回的是字符串,使用不是很方便,所以这里还需要解析成对象。 通过split先拆分;拿到每一组,再通过=拆分拿到key和value。我们尝试一下,要是想删除 cookie 的话可以前往 application删。
回到 user.js 我们来规定一下如果 cookie 有 username 就是登 录成功,否则就失败,定义一个测试的路径,记得我们自己所有路由都改成了需要返回 promise ,如果没有封装 promise 可以利用 Promise.resolve 封装一些。那么后端如何修改 cookie ,通过 res 的 setHeader 方法设置 Set-Cookie 和 path=/(如此则所有的网站网页的路由都会生效)。说一下,首先是在 app.js 解析 cookie 方便各地使用。
/解析cookie req.cookie=//定文一个存放cookie的对象 const cookiestr = req.headers.cookie |l"//拿到cookie字符串或者空字符串cookiestr.split('; ').forEach(item => {//通过;分割出一组cookie if (litem){ return const arr = item.split('=')1/通过-分割每一组cookie的key和vaLueconst key = arr[o] const value = arr[1]req.cookie[key] = value)) console.log(req.cookie)//到此各地皆可以使用req.cookie来获取已经解析完成的cookie了
接着是在 router 里面对是否登录进行验证(主要是查看是否 有 username 存在,注意有可能会因为 username 被当成字符串导致判断失败),这一步可以手动加 cookie,对测试进行验证.
if (method === 'GET' &8 req.path === '/api/user/login-test') { if (req.cookie.username){//通过查看cookie里面有没有username这个key判断是否登录成功(注意有个 return Promise.resolve(new successMode1('登录成功')) return Promise.resolve(new ErrorModel(尚未登录')) }
在上面的验证可以生效后我们就可以使用了,在用户登录的 时候同时设置好 cookie,且设置为根路径方便 3000 端口下的都可以访问到,这一步的话由于改成 GET,需要在路径加上密码用户名,访问后就设置好了 cookie,再回 test 测试是否可以成功验证.
4.cookie做限制
然而目前还是有一个问题,就是 js 可以很轻易的设置document.cookie 来覆盖之前的 cookie 伪造身份。 那么怎么去限制呢?很简单,后端设置 cookie 的时候加多一个 httpOnly 即只允许通过服务端修改不允许通过客户端修改。这个时候 HTTP 就会多一个√,且不仅仅是看不见也不 能修改(指的是客户端)。
按理说不应该被修改,我们最后发现其实是被改的那个前面被加了个空格,这是因为新增cookie 前面都会有这个空格(如果前面有值)很简单,只需要 trim ()去掉几空格就行。 现在在客户端修改的话当然改什么就会在 cookie 出现什么,只不过被锁死的还是锁死,无法被覆盖。 另外,也不应该一直存着 cookie ,应该有个过期时间比较合适,我们定义一个 getCookieExpires 函数,获取当前时间, 并且再加上 24 个小时,返回的话改成 toGMTString 时间格式。 然后就可以回去设置 cookie 的 expire 即过期时间直接等于这个函数就行。
5.session介绍
将一下 cookie 的问题:危险的个人信息,极小的可存空间。session 是储存登录/会话信息的统称,通过 cookie 里面的 id去服务端对应一个具体的信息。
session:
- 上—节的问题:会暴露username ,很危险
- 如何解决:cookie中存储userid , server 端对应username
- 解决方案:session ,即server端存储用户信息
session
如上图,浏览器中 cookie 提供了 id,传到服务端,服务端可 以有 xxx 对应什么内容,yyy 对应什么内容的约定等等。 回到 app.js 定义一个 SESSION_DATA 的常量,在解析 cookie 后就可以来解析这个 session,判断 cookie 是否有 userId,有 则判断常量里面是否存在 userId,如果不存在则将常量的userId 属性设置为空对象,再赋值给我们的 req.session。如果 cookie 没有 userId 我们就随便定义一个时间戳再去赋值给常量里面的 userId 最后再赋值给我们的 req.session。
这个还是加多一个判断,定义一个 needSetCookie 状态,看看是否需要设置 cookie(cookie 不存在 userId 的时候就要), 实现在 blogs 和 users 的路由,把 userId 设置到 cookie 上。 那么怎么使用呢?这个就需要到登录那里原本设置cookie的可以去掉了,改为设置 session,同时验证登录的时候也改成判断 session。
6.session演示
上面有报错啊,有两个问题,第一个就是获取 cookie 时间的函数没有拷贝过来,第二个就是状态和 userId 后面都会改不能定义成常量,改成 let ! 注意 userId 的话是不管访问哪个路由都会有(因为路由们都会经过那个 serverHandle 共同的函数)。当然并不是每次刷新都会设置 cookie ,第一次就会,之后判断存在 userId 就不走设置 cookie 的分支了。所以我来捋一下,首先我们访问的是 login-test ,先走的是解析 session ,判断不存在 userId 且定义了 userId (第一次才会,之直接走给 req 赋值 session ),搞定去 login-test 路由,判断当前不存在 username 所以是未登录,回到 users 总路由设置 cookie 。接下来走 login 路由,先判断 session 存在了,赋值给 req ,走 login 路由,填充 session ,完成回到 user 总路由发送内容。再走一次 login-test ,判断 session 中有这个username 所以判断登录成功,自始至终只有 userId 暴露在外,具体拿到这个 userId 怎么处理是只有服务端才能看到。
总计:
- 知道session解决什么问题;
- 如何实现session
所以说 cookie 中 userId 只是为了给我这个专属的 userId 开辟 一个独特的存放 session 的空间,而 session 则是保存了实际的信息。
7.从session到Redis
当前写的 session 还是有问题的。目前本地的 node.js 当然是只有一个进程,但是上线多进程(甚至是多机房什么的)可能导致我们呃,第一次登录到其中一个进程,第二次登录却是另一个进程而失败。
session的问题:
- 目前session直接是js 变量,放在nodejs进程内存中
- 第一,进程内存有限,访问量过大,内存暴增怎么办?
- 第二,正式线上运行是多进程,进程之间内存无法共享
如图就是进程的内存模型,其中 0x1000 是起始地址,0x8000 是结束地址,stack 储存的是程序运行的基础变量(js 的基础 变量),heap 存的是程序运行的引用变量(js 的数组对象什么的,session 也放在这里),存的越多,占的就越多(越高) 如果上下接起来那就崩了。
进程内存模型
操作系统会限制一个进程的最大可用内存(大概是 3G),而 session 是存在一个 node.js 进程的,不可能无限储存,一旦存的太多,撑爆 node.js 进程就 GG。
上线的时候每个 node.js 程序都是分多个进程来跑的,因为 单个进程占的内存有限,也浪费其它的内存。而且多核的处 理器可以处理多个进程。这是符合内存与 cpu 的实际情况。 当然进程之间是不能互相访问的,不能共享数据什么的(你 个 QQ 进程总不能去访问我微信进程的数据吧)。回到 node.js,其实每次访问的进程是不一定的(PM2 分配进程, 都挤一个迟早升天),这就导致我在其中一个进程有数据, 到另一个就没有数据了。
存放内存中意味着读取很快但是也比较昂贵,空间少,一断电就没了(易丢失)
解决方案Redis:
- web server最常用的缓存数据库,数据存放在内存中
- 相比于mysql,访问速度快(内存和硬盘不是一个数量级的)
- 但是成本更高,可存储的数据量更小(内存的硬伤)
也就是说 redis(内存数据库)和 mysql(硬盘数据库)都是储存数据的;
解决方案Redis
解决方案Redis:
- 将web server和redis 拆分为两个单独的服务
- 双方都是独立的,都是可扩展的(例如都扩展成集群)
- (包括mysql ,也是一个单独的服务,也可扩展)
解决问题就是将session存放到redis中就行(之前是存在web server)。而既然分开了,redis 储存多少就不管 node.js 进程的事情了(因为分开了啊,变成两个东西),再说访问,因 为就是这一个 redis,不管谁访问还是访问这一个,也就一份数据,不管是哪个进程访问都给一样的数据。就像是一个独立的仓库。就算是太多了,redis 也可以拆分成集群/机房什么的,不需要我们关心。不考虑丢失数据的意思是重新登录即可。
为何session适合用Redis?
- session访问频繁,对性能要求极高
- session可不考虑断电丢失数据的问题(内存的硬伤)
- session数据量不会太大(相比于mysql 中存储的数据)
- 为何网站数据不适合使用Redis?
- 操作频率不是太高(相比于session操作)
- 断电不能丢失,必须保留
- 数据量太大,内存成本太高