xml地图|网站地图|网站标签 [设为首页] [加入收藏]
应用中的身份验证技术,浅谈前端工程化
分类:web前端

Websocket API:

这里是指客户端 API。

传统Web应用中的单点登录

单点登录的需求在向用户提供多种服务的企业普遍存在,出发点是希望用户在一个站点中登录之后,在其他兄弟站点中就不需要再次登录。

如果多个子站所在的顶级域名一致,基于上文所述的实践,可以基于Cookie共享实现最简单的单点登录:在多个子站中使用相同的加密、解密配置,并且在用户登录成功后设置身份 Cookie时将domain值设置为顶级域名即可。这样,只要在其中一个网站登录,其身份 Cookie将在用户访问其他子站时也一起带上。不过实际情况中,这个方案的应用场景很有限,毕竟各个子站使用的用户数据模型可能不完全一致,而加密密钥多处共享也增加了服务器应用程序的安全风险。另外,这种方式与“在多个网站中分别存储相同的用户名与密码”的做法相似,可以说是一种“相同的登录”(Same Sign-On),而不是“单点登录”(Single Sign-On)。

对于单点登录需求来说,域名相同与否并不是最大的挑战,集成登录系统对各个子站点的系统在设计上的影响才是。我们希望便利用户的同时,也期待各个子系统仍拥有独立用户身份、独立管理和运维的灵活性。因此我们引入独立的鉴权子站点。

图片 1

当用户到达业务站点A时,被重定向到鉴权站点;登录成功之后,用户被重定向回到业务站点 A、同时附加一个指示“已有用户登录”的令牌串——此时业务站点A使用令牌串,在服务器端从鉴权子站点查询并记录当前已登录的用户。当用户到达业务站点B时,执行相同流程。由于已有用户登录,所以用户登录的过程会被自动省略。

这样的单点登录系统能够较好地解决在多个站点中共享用户登录状态的需求。不过,如果在编程实践过程中略有差池,就会让用户陷入巨大的安全风险中。例如,在上述重定向过程中,一旦鉴权系统未能验证返回URL的合法性,就容易导致用户被钓鱼网站利用。在传统Web应用开发实践中,被广泛部署的身份验证体系是比较重量级的WS-Federation 和 SMAL 等鉴权协议和相对轻量级的 OpenID 等技术。

Hybrid载入

如果是Hybrid的话,情况有所不同,需要将公共资源打包至native中,业务类就不要打包了,否则native会越来越大。

Websocket是什么:

RFC中写到:WebSocket协议使在控制环境下运行不受信任代码的客户端和能够选择与那些代码通信的远程主机之间能够双向通信。

对,划重点:双向通信

Websocket在连接之后,客户端可以主动发送消息给服务器,服务器也可以主动向客户端推送消息。比如:预订车票信息,除了我们发请求询问车票如何,当然更希望如果有新消息,可以直接通知我们。

其特点:

(1)握手阶段采用 HTTP 协议,默认端口是80和443

(2)建立在TCP协议基础之上,和http协议同属于应用层

(4)可以发送文本,也可以发送二进制数据

(5)没有同源限制,客户端可以与任意服务器通信

(6)协议标识符是ws(如果加密,为wss),如ws://localhost:8023

简单来说,Websocket协议分为两部分:握手和数据传输。

图片 2

简单实用的登录技术

对于互联网Web应用来说,不采用Basic或Digest鉴权的理由主要有两个:

  1. 不能接受在每个请求中发送用户名和密码凭据
  2. 需要在服务器端对密码进行不可逆的加密

因此,互联网Web应用开发已经形成了一个基本的实践模式,能够在服务端对密码强加密之后存储,并且尽量减少鉴权过程中对凭据的传输。其过程如下图所示:

图片 3

这一过程的原理很简单,专门发送一个鉴权请求,只在这个请求头中包含原始用户名和密码凭据,经服务器验证合法之后,由服务器发给一个会话标识(Session ID),客户端将会话标识存储在 Cookie 中,服务器记录会话标识与经过验证的用户的对应关系;后续客户端使用会话标识、而不是原始凭据去与服务器交互,服务器读取到会话标识后从自身的会话存储中读取已在第一个鉴权请求中验证过的用户身份。为了保护用户的原始凭据在传输中的安全,只需要为第一个鉴权请求构建安全连接支持。

服务端的代码包含首次鉴权和后续检查并授权访问的过程:

IUser _user_; if( validateLogin( nameFromReq, pwdFromReq, out _user _)){ Session["CurrentUser"] = _user_; }

1
2
3
4
5
IUser _user_;  
if( validateLogin( nameFromReq, pwdFromReq, out _user _)){  
  Session["CurrentUser"] = _user_;  
}
 

(首次鉴权)

IUser _user_ = Session["CurrentUser"] as IUser; if( _user_ == null ){ Response.Redirect( "/login?return_uri=" + Request.Url.ToString() ); return; }

1
2
3
4
5
6
7
IUser _user_ = Session["CurrentUser"] as IUser;  
if( _user_ == null ){  
     Response.Redirect( "/login?return_uri=" +
     Request.Url.ToString() );  
     return;  
}
 

(后续检查并拒绝未识别的用户)

类似这样的技术简易方便,容易操作,因此大量被运用于很多互联网Web应用中。它在客户端和传输凭据过程中几乎没有做特殊处理,所以在这两个环节尤其要注意对用户凭据的保护。不过,随着我们对系统的要求越来越复杂,这样简易的实现方式也有一些明显的不足。比如,如果不加以封装,很容易出现在服务器应用程序代码中出现大量对用户身份的重复检查、错误的重定向等;不过最明显的问题可能是对服务器会话存储的依赖,服务器程序的会话存储往往在服务器程序重启之后丢失,因此可能会导致用户突然被登出的情况。虽然可以引入单独的会话存储程序来避免这类问题,但引入一个新的中间件就会增加系统的复杂性。

拦路虎

有一些网站初期比较快,但是随着量的积累,BUG越来越多,速度也越来越慢,一些前端会使用上述优化手段做优化,但是收效甚微,一个比较典型的例子就是代码冗余:

① 之前的CSS全部放在了一个文件中,新一轮的UI样式优化,新老CSS难以拆分,CSS体量会增加,如果有业务团队使用了公共样式,情况更不容乐观;

② UI组件更新,但是如果有业务团队脱离接口操作了组件DOM,将导致新组件DOM更新受限,最差的情况下,用户会加载两个组件的代码;

③ 胡乱使用第三方库、组件,导致页面加载大量无用代码;

……

以上问题会不同程度的增加资源下载体量,如果听之任之会产生一系列工程问题:

① 页面关系错综复杂,需求迭代容易出BUG;

② 框架每次升级都会导致额外的请求量,常加载一些业务不需要的代码;

③ 第三方库泛滥,且难以维护,有BUG也改不了;

④ 业务代码加载大量异步模块资源,页面请求数增多;

……

为求快速占领市场,业务开发时间往往紧迫,使用框架级的HTML&CSS、绕过CSS Sprite使用背景图片、引入第三方工具库或者UI,会经常发生。当遇到性能瓶颈时,如果不从根源解决问题,用传统的优化手段做页面级别的优化,会出现很快页面又被玩坏的情况,几次优化结束后我也在思考一个问题:

前端每次性能优化的手段皆大同小异;代码的可维护性也基本是在细分职责; 既然每次优化的目的是相同的,每次实现的过程是相似的,而每次重新开发项目又基本是要重蹈覆辙的,那么工程化、自动化可能是这一切问题的最终答案

1
2
前端每次性能优化的手段皆大同小异;代码的可维护性也基本是在细分职责;
既然每次优化的目的是相同的,每次实现的过程是相似的,而每次重新开发项目又基本是要重蹈覆辙的,那么工程化、自动化可能是这一切问题的最终答案

工程问题在项目积累到一定量后可能会发生,一般来说会有几个现象预示着工程问题出现了:

① 代码编写&调试困难

② 业务代码不好维护

③ 网站性能普遍不好

④ 性能问题重复出现,并且有不可修复之势

像上面所描述情况,就是一个典型的工程问题;定位问题、发现问题、解决问题是我们处理问题的手段;而如何防止同一类型的问题重复发生,便是工程化需要做的事情,简单说来,优化是解决问题,工程化是避免问题,今天我们就站在工程化的角度来解决一些前端优化问题,防止其死灰复燃。

文中是我个人的一些开发经验,希望对各位有用,也希望各位多多支持讨论,指出文中不足以及提出您的一些建议

HTML5的Websocket(理论篇 I)

2017/10/28 · HTML5 · websocket

原文出处: 转转前端   

先请来TA的邻居:

http:无状态、基于tcp请求/响应模式的应用层协议 (A:哎呀,上次你请我吃饭了么? B:我想想, 上次请你吃了么)
tcp:面向连接、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达) 传输层协议。(看啊,大阅兵,如此规整有秩序)

为什么要引入Websocket:

RFC开篇介绍:本协议的目的是为了解决基于浏览器的程序需要拉取资源时必须发起多个HTTP请求和长时间的轮询的问题。

long poll(长轮询): 客户端发送一个request后,服务器拿到这个连接,如果有消息,才返回response给客户端。没有消息,就一直不返回response。之后客户端再次发送request, 重复上次的动作。

图片 4

从上可以看出,http协议的特点是服务器不能主动联系客户端,只能由客户端发起。它的被动性预示了在完成双向通信时需要不停的连接或连接一直打开,这就需要服务器快速的处理速度或高并发的能力,是非常消耗资源的。

这个时候,Websocket出现了。

总结

本文简要总结了在传统Web应用中,被广泛使用的几种典型用户登录时的鉴权处理流程。总体来说,在单体 Web 应用中,身份验证过程并不复杂,只要稍加管理,可以较轻松地解决用户鉴权的问题。但在传统 Web 应用中,为了解决单点登录的需求,人们也尝试了多种方式,最终仍然只有使用一些较复杂的方案才能较好地解决问题。

在现代化 Web 应用中,围绕登录这一需求,俨然已经衍生出了一个新的工程。“登录工程” 并不简单,在后续篇目中将会介绍现代化 Web 应用的典型需求及解决方法。

1 赞 4 收藏 评论

资源加载

解决冗余便抛开了历史的包袱,是前端优化的第一步也是比较难的一步,但模块拆分也将全站分成了很多小的模块,载入的资源分散会增加请求数;如果全部合并,会导致首屏加载不需要的资源,也会导致下一个页面不能使用缓存,如何做出合理的入口资源加载规则,如何合理的善用缓存,是前端优化的第二步。

经过几次性能优化对比,得出了一个较优的首屏资源加载方案:

① 核心框架层:mvc骨架、异步模块加载器(require&seajs)、工具库(zepto、underscore、延迟加载)、数据请求模块、核心依赖UI(header组件消息类组件)

② 业务公共模块:入口文件(require配置,初始化工作、业务公共模块)

③ 独立的page.js资源(包含template、css),会按需加载独立UI资源

④ 全局css资源

图片 5

这里如果追求极致,libs.js、main.css与main.js可以选择合并,划分结束后便可以决定静态资源缓存策略了。

Websocket事件

WebSocket 是纯事件驱动,通过监听事件可以处理到来的数据和改变的连接状态。服务端发送数据后,消息和事件会异步到达。

  • open:
    服务端响应WebSocket连接请求,就会触发open事件。onopen是响应的回调函数。
JavaScript

// 连接请求open事件处理: ws.onopen = e => {
console.log('Connection success'); ws.send(`Hello ${e}`); };

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b5b531196143-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b5b531196143-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b5b531196143-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b5b531196143-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b5b531196143-5">
5
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b5b531196143-1" class="crayon-line">
 // 连接请求open事件处理:
</div>
<div id="crayon-5b8f447934b5b531196143-2" class="crayon-line crayon-striped-line">
     ws.onopen = e =&gt; {
</div>
<div id="crayon-5b8f447934b5b531196143-3" class="crayon-line">
         console.log('Connection success');
</div>
<div id="crayon-5b8f447934b5b531196143-4" class="crayon-line crayon-striped-line">
         ws.send(`Hello ${e}`);
</div>
<div id="crayon-5b8f447934b5b531196143-5" class="crayon-line">
     };
</div>
</div></td>
</tr>
</tbody>
</table>

如果要指定多个回调函数,可以使用addEventListener方法。

JavaScript

ws.addEventListener('open', e => { ws.send(`Hello ${e}`); });

1
2
3
ws.addEventListener('open', e => {
  ws.send(`Hello ${e}`);
});

当open事件触发时,意味着握手阶段已结束。服务端已经处理了连接的请求,可以准备收发数据。

  • Message:收到服务器数据,会触发消息事件,onmessage是响应的回调函数。如下:
JavaScript

// 接受文本消息的事件处理: ws.onmessage = e =&gt; { const data =
e.data; if (typeof data === "string") { console.log("Received string
message ",data); } else if (data instanceof Blob) {
console.log("Received blob message ", data); } };

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b62129912854-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b62129912854-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b62129912854-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b62129912854-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b62129912854-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b62129912854-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b62129912854-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b62129912854-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b62129912854-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b62129912854-1" class="crayon-line">
// 接受文本消息的事件处理:
</div>
<div id="crayon-5b8f447934b62129912854-2" class="crayon-line crayon-striped-line">
ws.onmessage = e =&gt; {
</div>
<div id="crayon-5b8f447934b62129912854-3" class="crayon-line">
    const data = e.data;
</div>
<div id="crayon-5b8f447934b62129912854-4" class="crayon-line crayon-striped-line">
    if (typeof data === &quot;string&quot;) {
</div>
<div id="crayon-5b8f447934b62129912854-5" class="crayon-line">
        console.log(&quot;Received string message &quot;,data);
</div>
<div id="crayon-5b8f447934b62129912854-6" class="crayon-line crayon-striped-line">
    } else if (data instanceof Blob) {
</div>
<div id="crayon-5b8f447934b62129912854-7" class="crayon-line">
        console.log(&quot;Received blob message &quot;, data);
</div>
<div id="crayon-5b8f447934b62129912854-8" class="crayon-line crayon-striped-line">
    }
</div>
<div id="crayon-5b8f447934b62129912854-9" class="crayon-line">
};
</div>
</div></td>
</tr>
</tbody>
</table>

服务器数据可能是文本,也可能是二进制数据,有Blob和ArrayBuffer两种类型,在读取到数据之前需要决定好数据的类型。

  • Error发生错误会触发error事件, onerror是响应的回调函数, 会导致连接关闭。
JavaScript

//异常处理 ws.onerror = e =&gt; { console.log("WebSocket Error: " ,
e); handleErrors(e); };

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b66862080563-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b66862080563-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b66862080563-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b66862080563-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b66862080563-5">
5
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b66862080563-1" class="crayon-line">
//异常处理
</div>
<div id="crayon-5b8f447934b66862080563-2" class="crayon-line crayon-striped-line">
ws.onerror = e =&gt; {
</div>
<div id="crayon-5b8f447934b66862080563-3" class="crayon-line">
    console.log(&quot;WebSocket Error: &quot; , e);
</div>
<div id="crayon-5b8f447934b66862080563-4" class="crayon-line crayon-striped-line">
    handleErrors(e);
</div>
<div id="crayon-5b8f447934b66862080563-5" class="crayon-line">
};
</div>
</div></td>
</tr>
</tbody>
</table>
  • Close当连接关闭时触发close事件,对应onclose方法,连接关闭之后,服务端和客户端就不能再通信。

WebSocket 规范中定义了ping 帧 和pong 帧,可以用来做心跳重连,网络状态查询等,但是目前 浏览器只会自动发送pong帧,而不会发ping 帧。(有兴趣可详查ping和pong帧)

JavaScript

//关闭连接处理 ws.onclose = e => { const code = e.code; const reason = e.reason; console.log("Connection close", code, reason); };

1
2
3
4
5
6
//关闭连接处理
ws.onclose = e => {
    const code = e.code;
    const reason = e.reason;
    console.log("Connection close", code, reason);
};

传统 Web 应用中的身份验证技术

2016/12/13 · 基础技术 · WEB, 身份验证

本文作者: 伯乐在线 - ThoughtWorks 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

标题中的 “传统Web应用” 这一说法并没有什么官方定义,只是为了与“现代化Web应用”做比较而自拟的一个概念。所谓“现代化Web应用”指的是那些基于分布式架构思想设计的,面向多个端提供稳定可靠的高可用服务,并且在需要时能够横向扩展的Web应用。相对而言,传统Web应用则主要是直接面向PC用户的Web应用程序,采用单体架构较多,也可能在内部采用SOA的分布式运算技术。

一直以来,传统Web应用为构成互联网发挥了重要作用。因此传统Web应用中的身份验证技术经过几代的发展,已经解决了不少实际问题,并最终沉淀了一些实践模式。

图片 6

在讲述多种身份鉴权技术之前,要强调一点:在构建互联网Web应用过程中,无论使用哪种技术,在传输用户名和密码时,请一定要采用安全连接模式。因为无论采用何种鉴权模型,都无法保护用户凭据在传输过程中不被窃取。

seed.js时代

突然一天框架发现一个全局性BUG,并且马上做出了修复,业务团队也马上发布上线,但这种事情出现第二次、第三次框架这边便压力大了,这个时候框架层面希望业务只需要引用一个不带缓存的seed.js,seed.js要怎么加载是他自己的事情:

<script type="text/javascript" src="seed.js"></script>

1
<script type="text/javascript" src="seed.js"></script>

//seed.js需要按需加载的资源 <script src="libs_md5.js"></script> <script src="main_md5.js"></script>

1
2
3
//seed.js需要按需加载的资源
<script src="libs_md5.js"></script>
<script src="main_md5.js"></script>

当然,由于js加载是顺序是不可控的,我们需要为seed.js实现一个最简单的顺序加载模块,映射什么的由构建工具完成,每次做覆盖发布即可,这样做的缺点是额外增加一个seed.js的文件,并且要承担模块加载代码的下载量。

WebSocket 方法:

WebSocket 对象有两个方法:send 和 close

  • send:客户端和服务器建立连接后,可以调用send方法去发送消息。
JavaScript

//发送一个文本消息 ws.send("this is websocket");

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b6d916593124-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b6d916593124-2">
2
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b6d916593124-1" class="crayon-line">
//发送一个文本消息
</div>
<div id="crayon-5b8f447934b6d916593124-2" class="crayon-line crayon-striped-line">
ws.send(&quot;this is websocket&quot;);
</div>
</div></td>
</tr>
</tbody>
</table>

在open事件的回调中调用send()方法传送数据:

JavaScript

const ws = new WebSocket('ws://localhost:8023'); ws.onopen = e => { console.log('Connection success'); ws.send(`Hello ${e}`); };

1
2
3
4
5
const ws = new WebSocket('ws://localhost:8023');
ws.onopen = e => {
    console.log('Connection success');
    ws.send(`Hello ${e}`);
};

如果想通过响应其他事件发送消息,可通过判断当前的Websocket的readyState属性。接下来会说到readyState.

  • closeclose方法用来关闭连接。调用close方法后,将不能发送数据。close方法可以传入两个可选的参数,code 和reason, 以告诉服务端为什么终止连接。
JavaScript

ws.close(); //1000是状态码,代表正常结束。 ws.close(1000, "Closing
normally");

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b73487491254-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b73487491254-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b73487491254-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b73487491254-4">
4
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b73487491254-1" class="crayon-line">
ws.close();
</div>
<div id="crayon-5b8f447934b73487491254-2" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f447934b73487491254-3" class="crayon-line">
//1000是状态码,代表正常结束。
</div>
<div id="crayon-5b8f447934b73487491254-4" class="crayon-line crayon-striped-line">
ws.close(1000, &quot;Closing normally&quot;);
</div>
</div></td>
</tr>
</tbody>
</table>

传统Web应用中身份验证最佳实践

上文提到的简单实用的登录技术已经可以帮助建立对用户身份验证的基本图景,在一些简单的应用场景中已经足够满足需求了。然而,用户鉴权就是有那种“你可以有很多种方法,就是不怎么优雅” 的问题。

最佳实践指的是那些经过了大量验证、被证明有用的方法。而用户鉴权的最佳实践就是使用自包含的、含有加密内容的 Cookie 作为替代凭据。其鉴权过程与上文所提到基于会话标识的技术没有什么区别,而主要区别在于不再颁发会话标识,取而代之的是一个代表身份的、经过加密的 “身份 Cookie”。

图片 7

  1. 只在鉴权请求中发送一次用户名和密码凭据
  2. 成功凭据之后,由服务器生成代表用户身份的 Cookie,发送给客户端
  3. 客户端在后续请求中携带上一步中收到的 “身份 Cookie”
  4. 服务器解密”身份 Cookie”,并对需要访问的资源予以授权

这样,我们消除了对服务器会话存储的依赖,Cookie本身就有有效期的概念,因此顺便能够轻松提供“记住登录状态”的功能。

另外,由于解密Cookie、既而检查用户身份的操作相对繁琐,工程师不得不考虑对其抽取专门的服务,最终采用了面向切面的模式对身份验证的过程进行了封装,而开发时只需要使用一些特性标注(Attribute Annotation)对特定资源予以标记,即可轻松完成身份验证预处理。

Rendering工具

Chrome还有一款工具为分析渲染而生:

图片 8

Show paint rectangles 显示绘制矩形 Show composited layer borders 显示层的组合边界 Show FPS meter 显示FPS帧频 Enable continuous page repainting 开启持续绘制模式 并 检测页面绘制时间 Show potential scroll bottlenecks 显示潜在的滚动瓶颈。

1
2
3
4
5
Show paint rectangles 显示绘制矩形
Show composited layer borders 显示层的组合边界
Show FPS meter 显示FPS帧频
Enable continuous page repainting 开启持续绘制模式 并 检测页面绘制时间
Show potential scroll bottlenecks 显示潜在的滚动瓶颈。

show paint rectangles

开启矩形框,便会有绿色的框将页面中不同的元素框起来,如果页面渲染便会整块加深,举个例子:

图片 9

当点击+号时,三块区域产生了重绘,这里也可以看出,每次重绘都会影响一个块级(Layer),连带反应会影响周边元素,所以一次mask全局遮盖层的出现会导致页面级重绘,比如这里的loading与toast便有所不同:

图片 10

图片 11

loading由于遮盖mask的出现而产生了全局重绘,而toast本身是绝对定位元素只影响了局部,这里有一个需要注意的是,因为loading转圈的动画是CSS3实现的,虽然不停的再动,事实上只渲染了一次,如果采用javascript的话,便会不停重绘。

然后当页面发生滚动时,下面的支付工具条一直呈绿色状态,意思是滚动时一直在重绘,这个重绘的频率很高,这也是fixed元素相当耗费性能的原因:

图片 12

结合Timeline的渲染图

图片 13

如果这里取消掉fixed元素的话:

图片 14

这里fixed元素支付工具栏滚动时候是绿的,但是同样是fixed的header却没有变绿,那是因为header多了一个css属性:

CSS

.cm-header { -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); }

1
2
3
4
.cm-header {
    -webkit-transform: translate3d(0,0,0);
    transform: translate3d(0,0,0);
}

这个属性会创建独立的Layer,有效的降低了fixed属性的性能损耗,如果header去掉此属性的话,就不一样了:

图片 15

show composited layer borders

显示组合层边界,是因为页面是由多个图层组成,勾上后页面便开始分块了:

图片 16

使用该工具可以查看当前页面Layer构成,这里的+号以及header都是有自己独立的图层的,原因是使用了:

CSS

transform: translate3d(-50%,-50%,0);

1
transform: translate3d(-50%,-50%,0);

Layer存在的意义在于可以让页面最优的方式绘制,这个是CSS3硬件加速的秘密,就如header一样,形成Layer的元素绘制会有所不同。

Layer的创建会消耗额外的资源,所以不能不加节制的使用,以上面的“+”来说,如果使用icon font效果也许更好。

因为渲染这个东西比较底层,需要对浏览器层面的了解更多,关于更多更全的渲染相关知识,推荐阅读我好友的博客:

WebSocket 属性

  • readyState:

readyState值表示连接状态,是只读属性。它有以下四个值:

WebSocket.CONNECTING :连接正在进行,但还没有建立
WebSocket.OPEN :连接已经建立,可以发送消息
WebSocket.CLOSING :连接正在进行关闭握手
WebSocket.CLOSED :连接已经关闭或不能打开

除了在open事件回调中调用send方法,可通过判断readyState值来发送消息。

JavaScript

function bindEventHandler(data) { if (ws.readyState === WebSocket.OPEN) { ws.send(data); } else { //do something } }

1
2
3
4
5
6
7
function bindEventHandler(data) {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(data);
    } else {
        //do something
    }
}
  • bufferedAmount:当客户端传输大量数据时,浏览器会缓存将要流出的数据,bufferedAmount属性可判断有多少字节的二进制数据没有发送出去,发送是否结束。
JavaScript

ws.onopen = function () { setInterval( function() {
//缓存未满的时候发送 if (ws.bufferedAmount &lt; 1024 * 5) {
ws.send(data); } }, 2000); };

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f447934b7a325701025-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b7a325701025-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b7a325701025-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b7a325701025-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b7a325701025-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b7a325701025-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f447934b7a325701025-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f447934b7a325701025-8">
8
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f447934b7a325701025-1" class="crayon-line">
ws.onopen = function () {
</div>
<div id="crayon-5b8f447934b7a325701025-2" class="crayon-line crayon-striped-line">
    setInterval( function() {
</div>
<div id="crayon-5b8f447934b7a325701025-3" class="crayon-line">
        //缓存未满的时候发送
</div>
<div id="crayon-5b8f447934b7a325701025-4" class="crayon-line crayon-striped-line">
        if (ws.bufferedAmount &lt; 1024 * 5) {
</div>
<div id="crayon-5b8f447934b7a325701025-5" class="crayon-line">
            ws.send(data);
</div>
<div id="crayon-5b8f447934b7a325701025-6" class="crayon-line crayon-striped-line">
        }
</div>
<div id="crayon-5b8f447934b7a325701025-7" class="crayon-line">
    }, 2000);
</div>
<div id="crayon-5b8f447934b7a325701025-8" class="crayon-line crayon-striped-line">
};
</div>
</div></td>
</tr>
</tbody>
</table>
  • protocol:protocol代表客户端使用的WebSocket协议。当握手协议未成功,这个属性是空。

接下来,我们说说握手阶段过程。

当我们创建Websocket实例对象与服务器建立连接时,

JavaScript

const ws = new WebSocket('ws://localhost:8023');

1
const ws = new WebSocket('ws://localhost:8023');

首先客户端向服务器发起一个握手请求,其请求报文的内容如下:

JavaScript

GET /game HTTP/1.1 Host: 10.242.17.102:8023 Cache-Control: no-cache Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Protocol: game Sec-WebSocket-Version: 10 Origin: Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8

1
2
3
4
5
6
7
8
9
10
11
GET /game HTTP/1.1
Host: 10.242.17.102:8023
Cache-Control: no-cache
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: game
Sec-WebSocket-Version: 10
Origin: http://192.168.185.16
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

从请求头中可以看出,其实是一个基于http的握手请求。与通常的http请求不同的是,增加了一些头信息。

  • Upgrade字段:
    通知服务器,现在要使用一个升级版协议 – Websocket。
  • Sec-WebSocket-Key:
    是一个Base64编码的值,这个是浏览器随机生成,通知服务器,需要验证下是否可以进行Websocket通信
  • Sec_WebSocket-Protocol: 是用户自定义的字符串,用来标识服务所需要的协议
  • Sec-WebSocket-Version: 通知服务器所使用的协议版本

服务器响应:

当服务器返回以下内容,就表示已经接受客户端请求啦,可以建立Websocket通信啦。

JavaScript

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: SIEylb7zRYJAEgiqJXaOW3V+ZWQ=

1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: SIEylb7zRYJAEgiqJXaOW3V+ZWQ=
  • 101 状态码,表示要转换协议啦
  • Upgrde:
    通知客户端将要升级成Websocket协议
  • Sec-WebSocket-Accept:
    经过服务器确认,并且加密过后的 Sec-WebSocket-Key。用来证明客户端和服务器之间能进行通信了。

图片 17

至此,客户端和服务器握手成功建立了Websocket连接,通信不再使用http数据帧,而采用Websocket独立的数据帧。


以上是Websocket协议的基础理论篇I, 欢迎小伙伴儿们接力(理论篇II, 实战篇神马的), 一起学习一起积累


1 赞 4 收藏 评论

图片 18

关于作者:ThoughtWorks

图片 19

ThoughtWorks是一家全球IT咨询公司,追求卓越软件质量,致力于科技驱动商业变革。擅长构建定制化软件产品,帮助客户快速将概念转化为价值。同时为客户提供用户体验设计、技术战略咨询、组织转型等咨询服务。 个人主页 · 我的文章 · 84 ·   

图片 20

UI组件

UI组件本身包括完整的HTML&CSS&Javascript,一个复杂的组件下载量可以达到10K以上,就UI部分来说容易导致两个工程化问题:

① 升级产生代码冗余

② 对外接口变化导致业务升级需要额外开发

WebSocket 构造函数

通过调用WebSocket构造函数来创建一个WebSocket实例对象,建立客户端与服务器的连接。

JavaScript

const ws = new WebSocket('ws://localhost:8023');

1
const ws = new WebSocket('ws://localhost:8023');

Basic和Digest鉴权

基于HTTP的Web应用离不开HTTP本身的安全特性中关于身份鉴权的部分。虽然HTTP标准定义了好几种鉴权方式,但真正供Web应用开发者选择的并不多,这里简要回顾一下曾经被广泛运用过的Basic 和 Digest鉴权。

不知道读者是否熟悉一种最直接向服务器提供身份的方式,即在URL中直接写上用户名和密码:

1
2
http://user:passwd@www.server.com/index.html
 

这就是Basic鉴权的一种形式。

Basic和Digest是通过在HTTP请求中直接包含用户名和密码,或者它们的哈希值来向服务器传输用户凭据的方法。Basic鉴权直接在每个请求的头部或URL中包含明文的用户名或密码,或者经过Base64编码过的用户名或密码;而Digest则会使用服务器返回的随机值,对用户名和密码拼装后,使用多次MD5哈希处理后再向服务器传输。服务器在处理每个请求之前,读取收到的凭据,并鉴定用户的身份。

图片 21

Basic和Digest鉴权有一系列的缺陷。它们需要在每个请求中提供凭据,因此提供“记住登录状态”功能的网站中,不得不将用户凭据缓存在浏览器中,增加了用户的安全风险。Basic鉴权基本不对用户名和密码等敏感信息进行预处理,所以只适合于较安全的安全环境,如通过HTTPS安全连接传输,或者局域网。

看起来更安全的Digest在非安全连接传输过程中,也无法抵御中间人通过篡改响应来要求客户端降级为Basic鉴权的攻击。Digest鉴权还有一个缺陷:由于在服务器端需要核对收到的、由客户端经过多次MD5哈希值的合法性,需要使用原始密码做相同的运算,这让服务器无法在存储密码之前对其进行不可逆的加密。Basic 和Digest鉴权的缺陷决定了它们不可能在互联网Web应用中被大量采用。

拆分页面

一个PC业务页面,其模块是很复杂的,这个时候可以将之分为多个模块:

图片 22

一经拆分后,页面便是由业务组件组成,只需要关注各个业务组件的开发,然后在主控制器中组装业务组件,这样主控制器对页面的控制力度会增加。

业务组件一般重用性较低,会产生模块间的业务耦合,还会对业务数据产生依赖,但是主体仍然是HTML&CSS&Javascript,这部分代码也是经常导致冗余的,如果能按模块拆分,可以很好的控制这一问题发生:

图片 23

按照上述的做法现在的加载规则是:

① 公共样式文件

② 框架文件,业务入口文件

③ 入口文件,异步加载业务模块,模块内再异步加载其它资源

这样下来业务开发时便不需要引用样式文件,可以最大限度的提升首屏载入速度;需要关注的一点是,当异步拉取模块时,内部的CSS加载需要一个规则避免对其它模块的影响,因为模块都带有样式属性,页面回流、页面闪烁问题需要关注。

一个实际的例子是,这里点击出发后的城市列表便是一个完整的业务组件,城市选择的资源是在点击后才会发生请求,而业务组件内部又会细分小模块,再细分的资源控制由实际业务情况决定,过于细分也会导致理解和代码编写难度上升:

图片 24图片 25

demo演示地址,代码地址

如果哪天需求方需要用新的城市选择组件,便可以直接重新开发,让业务之间使用最新的城市列表即可,因为是独立的资源,所以老的也是可以使用的。

只要能做到UI级别的拆分与页面业务组件的拆分,便能很好的应付样式升级的需求,这方面冗余只要能避过,其它冗余问题便不是问题了,有两个规范最好遵守:

JavaScript

1 避免使用全局的业务类样式,就算他建议你使用 2 避免不通过接口直接操作DOM

1
2
1 避免使用全局的业务类样式,就算他建议你使用
2 避免不通过接口直接操作DOM

冗余是首屏载入速度最大的拦路虎,是历史形成的包袱,只要能消除冗余,便能在后面的路走的更顺畅,这种组件化编程的方法也能让网站后续的维护更加简单。

前端优化带来的思考,浅谈前端工程化

2015/10/26 · 前端职场 · 2 评论 · 工程化

原文出处: 叶小钗(@欲苍穹)   

消灭冗余

我们这里做的第一个事情便是消除优化路上第一个拦路虎:代码冗余(做代码精简),单从一个页面的加载来说,他需要以下资源:

① 框架MVC骨架模块&框架级别CSS

② UI组件(header组件、日历、弹出层、消息框……)

③ 业务HTML骨架

④ 业务CSS

⑤ 业务Javascript代码

⑥ 服务接口服务

因为产品&视觉会经常折腾全站样式加之UI的灵活性,UI最容易产生冗余的模块。

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:应用中的身份验证技术,浅谈前端工程化

上一篇:入门教程,控制台不完全指南澳门新葡亰手机版 下一篇:pwa重构上海地铁线路图,拖拽上传前传
猜你喜欢
热门排行
精彩图文