帮你理清 Web 应用的登录状态_leancloud web请求-程序员宅基地

技术标签: 应用开发  web  视频  acl  后端开发  

「LeanCloud Web 应用开发实践」系列直播及文章分享持续进行中。

每周二周四晚上 8 点开始,时长预计 45 分钟。在 “leanCloud通讯” 微信公众号回复 “公开课” 即可获取直播链接。

《LeanCloud Web 应用开发实践公开课》上期回顾和本期主题介绍。

点击查看完整公开课视频

抛出疑问 00:01:10

  • 在云引擎登录了,但是云函数却没有 currentUser
  • 在浏览器调用 JS SDK 登录用户,页面跳转时云引擎中没有 currentUser
  • 云引擎 SDK 中有些地方会有 fetchUser 属性,有什么用?

为了理清 currentUser 的状态,需要看下不同类型的 WEB 应用是如何运作的。

早期 WEB 应用——服务端渲染 00:02:40

使用云引擎 demo 来演示,可以使用 https://todo-demo.leanapp.cn 来做接下来的尝试,或者自己部署该 demo 应用尝试(代码 版本: 1efc44a )。

这个 demo 是一个典型的服务端渲染的应用。所谓的服务端渲染是指浏览器请求服务端的地址或资源时,服务端返回一个 HTML 文档(一个很大的字符串),浏览器收到 HTML 文档之后,进行渲染并呈现页面。通过云引擎的自定义路由很容易实现这样的 WEB 应用。

如果单纯看请求和响应,以登录页面为例:

$ curl -v https://todo-demo.leanapp.cn/users/login
> GET /users/login HTTP/1.1
> Host: todo-demo.leanapp.cn
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
<
<!DOCTYPE html><html><head><title>用户登录</title>...<input type="submit" 
value="登录" class="btn btn-default"><a href="/users/register" class="btn btn-default">注册</a></div></form></div></body></html> 
提示:为了方便表达,所有页面请求都转化为 curl 请求的方式,下同。
提示:为了节省空间,删掉了很多额外的内容(下同),可以自己执行 curl 命令看完整结果。

服务端如何感知登录用户? 00:07:41

提示:请勾选浏览器控制台 Network 标签页的 Preserve log 选项,这样之前的请求在页面跳转之后还会保留,方便观察。

先配置云引擎 cookieSession中间件代码):

app.use(AV.Cloud.CookieSession({ secret: '05XgTktKPMkU', maxAge: 3600000, fetchUser: true }));

用户登录路由的 代码 如下:

router.post('/login', function(req, res, next) {
    
  var username = req.body.username;
  var password = req.body.password;
  AV.User.logIn(username, password).then(function(user) {
    
    res.saveCurrentUser(user);
    res.redirect('/todos');
  }, function(err) {
    
    res.redirect('/users/login?errMsg=' + err.message);
  }).catch(next);
});

在云引擎的自定义路由中调用了 AV.User.logIn 的 API,并且调用了 res.saveCurrentUser(user); 来将用户信息写入 cookie。

整个请求和响应的流程:

  • 浏览器并提交表单的 username 和 password 信息,向服务器发起请求:
curl -v 'https://todo-demo.leanapp.cn/users/login' -H 'content-type: application/x-www-form-urlencoded' --data 'username=zhangsan&password=zhangsan'
  • 请求到达云引擎登录相关的路由,根据 username 和 password 进行登录:
var username = req.body.username;
var password = req.body.password;
AV.User.logIn(username, password)
  • 路由方法将用户信息写入 cookie:
res.saveCurrentUser(user);

该操作在最终请求响应时, cookieSession 中间件 会将用户的信息写入 header 的 Set-Cookie 中。

  • 浏览器收到响应:
< HTTP/1.1 302 Found
< Content-Type: text/plain; charset=utf-8
< Location: /todos
< Set-Cookie: avos:sess=eyJfdWlkIjoiNTUxZDJkZTZlNGIwYjM2NzFhZWNmZWIyIiwiX3Nlc3Npb25Ub2tlbiI6ImFjajd3eTgwdDhmdGtpYzRxYzY1ZDNiZDgifQ==; path=/; expires=Tue, 08 Aug 2017 15:49:21 GMT; secure; httponly
< Set-Cookie: avos:sess.sig=TyI_sXTvNa4nUSxByoX3zxWRZ8M; path=/; expires=Tue, 08 Aug 2017 15:49:21 GMT; secure; httponly
<

在响应里多了两个 Set-Cookie信息,收到这样的响应后,浏览器会在 cookie 里写入这些信息,其中 avos:sess对应的值是一个 base64 字符串,具体内容是 :

{"uid":"551d2de6e4b0b3671aecfeb2","sessionToken":"acj7wy80t8ftkic4qc65d3bd8"}

所以标示用户身份的 sessionToken 信息保存在 cookie 里。

提示:avos:sess.sig 是一个校验使用字符串,可以不关心。

cookie 有个特性:每次请求服务器时,会把 cookie 自动添加到请求的 header 中。所以之后再请求该站点的其他页面:

curl 'https://todo-demo.leanapp.cn/todos' -H 'cookie: avos:sess=eyJfdWlkIjoiNTUxZDJkZTZlNGIwYjM2NzFhZWNmZWIyIiwiX3Nlc3Npb25Ub2tlbiI6ImFjajd3eTgwdDhmdGtpYzRxYzY1ZDNiZDgifQ==; avos:sess.sig=TyI_sXTvNa4nUSxByoX3zxWRZ8M'

当这些请求到达云引擎应用之后, cookieSession 中间件 会再次起作用,从请求 header 中取出相关的 cookie 并校验,从中能获取到登录用户的 sessionToken ,然后从存储服务获取该用户的信息(或称为判断 sessionToken 是否有效),并将 user 信息赋值到 request.currentUser 属性上。

之后,请求会到达具体的自定义路由,此时就可以从 request.currentUser 获取发起请求的登录用户信息了。

小结 00:20:20

对于服务端渲染的应用:

  • 服务端响应整个 HTML,浏览器负责渲染并展现
  • 浏览器提交账号密码,服务端进行用户登录,并把代表用户身份的标示(比如 sessionToken)保存到 cookie 中。
  • 浏览器会保存服务端返回的 cookie,并在之后的请求中携带这些 cookie。
  • 服务端根据每次请求的 cookie 信息中判断是否有用户身份标示,并确认本次请求是否存在一个「当前登录用户」。

前后端分离的应用 00:22:10

服务端渲染的应用在用户体验方面存在不足,比如一系列表单填写完成之后一次性提交,此时服务端判断参数是否有效再响应用户;还有服务端每次响应整个 HTML 有很大的带宽浪费。之后出现了 AJAX 技术使得光标离开某个表单项之后,浏览器单独发送请求到服务端直接判断其有效性并迅速响应;并且每次浏览器与服务端通信都是一些数据结构(JSON 或者 XML)来降低流量,浏览器根据数据结果来修改 DOM 结构进行展现。

LeanCloud 将存储服务以 REST API 的方式提供服务,让前端(浏览器,或移动设备)可以方便的操作数据,这使得基于 LeanCloud 的应用基本都是前后端分离的。

当前示例使用一些简单页面来模拟前后端分离的应用。

前后端分离应用的请求 00:24:35

请求一个前后端分离的示例(页面代码):

$ curl 'https://todo-demo.leanapp.cn/static/page1.html'
<html>
  <head>
    <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  </head>
  <body>
    <h1>page1</h1>
    <script>
      ...
        console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))

        console.log('开始登录...')
        AV.User.logIn('zhangsan', 'zhangsan')
        .then(function(user) {
          console.log('登录成功: username: %s, sessionToken: %s', user.get('username'), user._sessionToken)
        })
        .then(function() {
          console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
      ...
    </script>
  </body>
</html>

服务端响应了一个页面,浏览器渲染页面时,会执行 script 部分的脚本,该脚本可能会做大量工作,比如生成或者修改页面 DOM,并向服务器发请求获取其他数据。比如这个示例就在页面打开之后 3 秒,通过 JS SDK 向服务器发起一个用户登录的请求,收到响应后在浏览器 console 输出一些日志。

提示:浏览器中可能会出现一些 OPTIONS 请求,具体原因见 HTTP访问控制(CORS)

使用浏览器请求 page1 ,整个流程如下:

  • 页面被渲染完成之后,也一起完成了 AV 对象的初始化工作。
var APP_ID = 'kdrt5GNCjojUjiIujawd5A4n-gzGzoHsz';
var APP_KEY = 'Xvxjo6SVUITIqet69q3mudlF';
AV.init({
 appId: APP_ID,
 appKey: APP_KEY
});
  • 3 秒之后,页面脚本通过 JS SDK 的 AV.User.logIn 方法向 LeanCloud 服务器发起登录请求。
setTimeout(function() {
 console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
 console.log('开始登录...')
 AV.User.logIn('zhangsan', 'zhangsan')
}, 3000)
  • 服务器响应用户信息:
{
 "sessionToken": "u2xtq3dxxvonapqn5uc9snbz7",
 "updatedAt": "2017-08-07T14:39:07.619Z",
 "objectId": "59887b8b570c350062430143",
 "username": "zhangsan",
 "createdAt": "2017-08-07T14:39:07.619Z",
 "emailVerified": false,
 "mobilePhoneVerified": false
}

JS SDK 将该信息反序列化构造出AV.User 对象,然后将其保存在浏览器 Local Storage 中。

通过 JS SDK 的 AV.User.current() 方法获取当前登录用户,本质上就是去 Local Storage 获取用户的信息并返回调用方(比如请求 page2页面代码):

...
console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
...
服务端如何感知登录用户 00:34:00

云函数 是运行在云引擎(服务端)的一个方法,通过 JS SDK 的 AV.Cloud.run 方法可以很方便的调用。

示例中定义了一个云函数(代码):

...
AV.Cloud.define('whoami', function(req, res) {
  console.log('whoami:', req.currentUser);
  var username = req.currentUser && req.currentUser.get('username');
  res.success(username);
});
...

在浏览器中通过 JS SDK 调用云函数(请求 page3页面代码):

...
AV.Cloud.run('whoami')
.then(function(username) {
  console.log('whoami:', username);
})
...

浏览器请求云函数流程如下:

  1. 通过 JS SDK 调用云函数,并根据需要传递参数(示例中未涉及)。JS SDK 会根据 Local Storage 中的信息在请求的 header 中附加 X-LC-Session ,值为用户身份标示 sessionToken。

  2. 请求到达云引擎应用,云引擎中间件会判断是否存在 X-LC-Session 的信息,如果有,就使用该值通过存储服务获取用户信息,并赋值给 request.currentUser。

  3. 请求进入云函数相关代码流程,开发者就可以获取到 currentUser 了:

console.log('whoami:', req.currentUser);
var username = req.currentUser && req.currentUser.get('username');
res.success(username);

因为使用 LeanCloud 的前后端分离应用,运行应用的域(比如云引擎的二级域名 http://abc.leanapp.cn )和提供服务的域(比如 LeanCloud 存储服务 https://api.leancloud.cn/1.1/class/Todo )不同,根据 cookie 的安全策略是不能在不同域传递 cookie 的。

所以 LeanCloud 的 SDK 会在请求的 header 中携带信息让服务端感知到当前登录用户。

小结 00:55:13

基于 LeanCloud 的前后端分离应用:

  • 使用云引擎返回「初始化状态」页面。
  • 浏览器通过 js 脚本决定如何渲染页面,经常是单页面应用。
  • 与服务端交互通过 REST API:由 JS SDK 封装,数据操作走存储服务,云函数操作走云引擎。
  • 因为 WEB 应用的域和服务端的域不同,用户状态不能通过 cookie 传递,而是通过请求 header 传递。

两种方式的对比 00:57:52

登录方式 云引擎自定义路由 浏览器 JS SDK + REST API(云函数)
保存位置 cookie Local Storage
服务端感知方式 通过 cookieSession 中间件 从 cookie 获取 通过云引擎中间件从 header 获取
与服务端交互方式 页面跳转或表单提交。因为同域,cookie 自动携带 通过 JS SDK 操作存储服务的数据或调用云函数。因为跨域,cookie 无法携带,使用 header。
服务端用户登录/登出操作 自定义路由中用户登录/登出后可以操作相关 cookie,浏览器 cookie 更新,影响后续请求。 云函数中用户登录/登出没有意义,不会改变浏览器 Local Storage 的内容,不影响后续浏览器对云函数的请求。

疑问解释 01:10:20

相信到这里,最初提出的疑问可以解释了:

  • 在云引擎登录了,但是云函数却没有 currentUser
    云引擎自定义路由登录只改变浏览器 cookie,而后续在浏览器通过 JS SDK 调用云函数时,是否携带 SessionToken 的信息在 header 中,和 cookie 无关。

  • 在浏览器调用 JS SDK 登录用户,页面跳转时云引擎中没有 currentUser
    浏览器调用 JS SDK 用户登录相关的 API 之后,只是 Local Storage 有变化,并在之后的访问存储服务或云函数时会将 sessionToken 携带在 header 中,cookie 并无变化。而应用页面跳转,或者 form 表单提交访问云引擎自定义路由时, cookieSession 中间件 无法从 cookie 中获取需要的信息。

服务端客户端用户感知同步 01:12:52

登录流程
  1. 浏览器调用服务端登录相关的路由,路由中登录用户,并更新 cookie,且响应中携带 sessionToken
  2. 浏览器收到登录响应,解析出 sessionToken,并调用 JS SDK 的 AV.User.become 方法在浏览器登录。

在此之后,不管是请求云引擎自定义路由还是请求云函数,都能确保 currentUser 的存在。当然 cookie 还存在过期的问题,不过这里就不展开讨论了。

登出流程
  1. 浏览器调用服务端登出路由,该路由可能做一些用户相关的资源清理,并清空 cookie。
  2. 浏览器受到登出响应后,调用 JS SDK 的相关方法在浏览器登出。

fetchUser 属性的作用 01:25:10

通过控制云引擎中间件的 fetchUser 属性,可以降低一部分不必要的 _User 的查询请求。

AV.Cloud.define API 为例,当收到云函数请求时,云引擎中间件从请求 header 中获取 sessionToken 信息,并且确认下 fetchUser 属性的值:

  • 如果为 true (默认):则使用 sessionToken从存储服务读取用户(_User 表)的信息。之后将 sessionTokencurrentUser 信息复制到 request 的相关属性上。
  • 如果为 false:则跳过从存储服务读取用户信息的步骤,只将 sessionToken 赋值到 request 的属性上。也就意味着云函数中 ```request.currentUserundefined
如何判断是否需要设置 fetchUser 的属性 01:33:00
  • 如果云函数的相关逻辑需要 _User 的其他信息,比如 username,那就设置 fetchUsertrue ,或者不设置使其保持默认值。

  • 否则,可以设置 fetchUserfalse ,但是需要在所有数据操作(和云函数调用)时将 sessionToken 加入到请求中:

var query = new AV.Query('Todo');
query.equalTo('status', 0);
query.find({sessionToken: req.sessionToken})

如果 req.sessionToken 有效,则存储服务会根据查询条件和 ACL 返回适当的信息。

如果 req.sessionToken 无效(过期或伪造),则存储服务可能因为 ACL 拒绝操作或返回空结果。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_16793755/article/details/77482146

智能推荐

星载/机载遥感导航论文合集-程序员宅基地

文章浏览阅读657次,点赞5次,收藏12次。采用现有无人车导航系统进行农作物育种表型信息的监测与采集时,存在因育种小区数量较多导致的导航目标点人工测量工作量大的问题,且育种田块不同于大田作物以A-B 线为基线的路径规划方法,无人车需要根据育种材料编号自动行进到某一指定位置进行作物表型信息采集。因此,为提高导航效率和降低人工劳动强度,本文基于无人机遥感对无人车的导航系统进行了研究。通过无人机获取玉米育种田的遥感影像并进行拼接和位置矫正,生成正射影像和数字地表模型(Digital Surface Model,DSM)。

asp.net mvc多条件+分页查询解决方案_.net core mvc+bootstrap 页面分页查询-程序员宅基地

文章浏览阅读7.8k次。开发环境vs2010css:bootstrapjs:jquery bootstrap paginator原先只是想做个mvc的分页,但是一般的数据展现都需要检索条件,而且是多个条件,所以就变成了MVC多条件+分页查询因为美工不是很好,所以用的是bootstrap前端框架,自己懒得写前端的分页控件,用的是bootstrap paginator分页控件。方式: _.net core mvc+bootstrap 页面分页查询

Springboot +spring security,OAuth2 四种授权模式概念_springbootsecurity oauth2-程序员宅基地

文章浏览阅读2.5k次。Springboot +spring security,OAuth2 四种授权模式概念_springbootsecurity oauth2

设计模式总结-程序员宅基地

文章浏览阅读78次。  在学习设计模式的过程中,实践需与理论相结合才能更好地举一反三,灵活运用。设计模式到底是什么?它是对整个软件系统的拆分,组装,并决定模块间关系以及如何互动、通信的某种模式。究其本质,设计模式就是以语言特性(面向对象三大特性)为硬件基础,再加持六大设计原则的灵魂组合而总结出的一系列套路,本篇要讲地就是灵魂。单一职责  我们知道功能完备的软件系统是复杂的,系统的拆分与模块化是不可或...

关于Token与JWT_token 与 jwt-程序员宅基地

文章浏览阅读1.1k次。Token:票据,令牌。当用户尝试登录,将请求提交到服务器端,如果服务器端认证通过,会生成一个Token数据并响应到客户端,此Token是有意义的数据,此客户端在后续的每一次请求中,都应该携带此Token数据,服务器端通过解析此Token来识别用户身份!关于Session与Token:Session默认是保存在服务器的内存中的数据,会占用一定的服务器内存资源,并且,不适合集群或分布式系统(虽然可以通过共享Session来解决),客户携带的Session ID只具有唯一性的特点(理论上),不具备数据含义……而_token 与 jwt

The new driver class is `com.mysql.cj.jdbc.Driver‘.-程序员宅基地

文章浏览阅读944次。记一次数据库连接数据库报错报错提示:Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class iscom.mysql.cj.jdbc.Driver’. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.原因:mysql驱动类D_the new driver class is `com.mysql.cj.jdbc.driver

随便推点

win10禁止google更新却在服务中找不到googleupdate,解决方案_服务里面没有谷歌更新服务-程序员宅基地

文章浏览阅读1.1w次,点赞2次,收藏3次。前提:想要使现用chrome不更新,按照网上最多的教程是控制面板-管理工具-服务-找到“Google更新服务(Gupdate)"服务 项与“Google更新服务(Gupdatem)”服务项-选择“禁用”我安装的谷歌浏览器在服务中找不到googleupdate参考教程如下:手动方式:chrome禁止更新如何设置_怎么设置chrome不自动更新-win7之家命令行方式:https://jingyan.baidu.com/article/f3ad7d0f51c6bf09c3345bb0.htm_服务里面没有谷歌更新服务

python人工智能算法pdf_深度学习:人工智能算法(Deep Learning) PDF 高清版-程序员宅基地

文章浏览阅读822次。给大家带来的一篇关于人工智能相关的电子书资源,介绍了关于深度学习、人工智能算法方面的内容,本书是由人民邮电出版社出版,格式为PDF,资源大小30.8 MB,Ian Goodfellow编写,目前豆瓣、亚马逊、当当、京东等电子书综合评分为:8.7。内容介绍本书包括3 个部分:第1 部分介绍基本的数学工具和机器学习的概念,它们是深度学习的预备知识;第2 部分系统深入地讲解现今已成熟的深度学习方法和技术..._人工智能 算法 pdf

Opencontrail CentOS66编译全过程_centos6 ipfix-程序员宅基地

文章浏览阅读1.2k次。(一)VNC1 下载如下包 git clonehttps://github.com/Juniper/contrail-testhttps://github.com/Juniper/contrail-provisioninghttps://github.com/Juniper/contrail-fabric-utilshttps://github.com/Juniper/cont_centos6 ipfix

c语言静态两个数码管显示0-99,按键控制计数,用两个数码管显示0到99。十位数为0的时候,显示为空白...-程序员宅基地

文章浏览阅读7.4k次,点赞3次,收藏39次。//用两个数码管显示0到99。十位数为0的时候,显示为空白。//用两个按键控制数值的加减,按一次K1数字加1,按一次K2数字减1,数值的范围是从0到99。//再用一个接近开关控制数字的减小,接近开关感应一次数字减1,减到0停止。// C语言程序如下。/*************************************************************** 文件 : -----..._两位数码管显示功能。具体功能描述如下:数码管可以显示0-99两位数字,按一下加按键

Android开发:设置背景图片_c#andriod 背景图-程序员宅基地

文章浏览阅读3w次,点赞7次,收藏12次。 在Android开发中,设置背景图片是一个既简单又常用的方法,由于本人现在需要同时做Android开发,所以Android的开发技能也要不断学习储备,不仅是为了给老板省钱,也是为了增加自己的竞争力。那么就来分享一下开发心得,给控件添加背景图的方法步骤,Android大牛请飘过,只分享给需要的人,今天分享在这里分享一下给添加背景图片的方法。 设置背景图片有两种方法,但是在..._c#andriod 背景图

网络通信技术-程序员宅基地

文章浏览阅读3.8k次,点赞2次,收藏12次。网络通信技术1. 基本概念1.1 接口1.2 表项查询方法1.2.1 内容寻址存储器2. 二层技术3. 三层技术1. 基本概念1.1 接口1.2 表项查询方法1.2.1 内容寻址存储器内容寻址存储器(Content Addressable Memory)CAM,是在传统的存储技术的基础上实现的联想记忆存储器,主要有读、写、查询操作。读操作:输入地址,返回该地址上的数据,读取速度与RAM相同;写操作:2. 二层技术3. 三层技术..._网络通信技术

推荐文章

热门文章

相关标签