`

JWT 在前后端分离中的应用与实践

 
阅读更多

本文主要介绍JWT(JSON Web Token)授权机制在前后端分离中的应用与实践,包括以下三部分:

  1. JWT原理介绍
  2. JWT的安全性
  3. React.js+Flux架构下的实践(React-jwt example

0 关于前后端分离

前后端分离是一个很有趣的议题,它不仅仅是指前后端工程师之间的相互独立的合作分工方式,更是前后端之间开发模式与交互模式的模块化、解耦化。计算机世界的经验告诉我们,对于复杂的事物,模块化总是好的,无论是后端API开发中越来越成为规范的RESTful API风格,还是Web前端越来越多的模板、框架(参见MVC,MVP 和 MVVM 的图示),包括移动应用中前后端天然分离的特质,都证实了前后端分离的重要性与必要性(更生动的细节与实例说明可以参看赫门分享的主题淘宝前后端分离实践)。

实现前后端分离,对于后端开发人员来说是一件很幸福的事情,因为不需要再考虑怎样在HTML中套入数据,只关心数据逻辑的处理;而前端则需要承担接收数据之后界面呈现、用户交互、数据传递等所有任务。虽然这看起来加重了前端的工作量,但实际上有越来越多丰富多样的前端框架可供选择,这让前端开发变得越来越结构化、系统化,前端工程师也不再只是“套版的”。

在所有前端框架中,Facebook推出的React无疑是当下最热门(之一),然而React只负责界面渲染层面,相当于MVC中的V(View),因此只靠React无法完成一个完整的单页应用(Single Page App)。Facebook另外推出与之配套的Flux架构,主要为了避免Angular.js之类MVC的架构模式,规避数据双向绑定而采用单向绑定的数据传递方式。实际上React无论是学习还是使用都是非常简单的,而Flux则需要花更多时间去理解消化,本文第3部分我采用Flux架构的一种实现Reflux.js,做了一个基于JWT授权机制的登入、登出的例子,顺便介绍Flux架构的细节。

1 JWT 介绍及其原理

JWT是我之前做Android应用的时候了解到的一种用户授权机制,虽然原生的移动手机应用与基于浏览器的Web应用之间存在很多差异,但很多情况下后端往往还是沿用已有的架构跟代码,所以用户授权往往还是采用Cookie+Session的方式,也就是需要原生应用中模拟浏览器对Cookie的操作。

Cookie+Session的存在主要是为了解决HTTP这一无状态协议下服务器如何识别用户的问题,其原理就是在用户登录通过验证后,服务端将数据加密后保存到客户端浏览器的Cookie中,同时服务器保留相对应的Session(文件或DB)。用户之后发起的请求都会携带Cookie信息,服务端需要根据Cookie寻回对应的Session,从而完成验证,确认这是之前登陆过的用户。其工作原理如下图所示:

Cookie+Session

JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,编码之后的JWT看起来是这样的一串字符:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ   

.分为三段,通过解码可以得到:

// 1. Headers // 包括类别(typ)、加密算法(alg); {   "alg": "HS256",   "typ": "JWT" } // 2. Claims // 包括需要传递的用户信息; {   "sub": "1234567890",   "name": "John Doe",   "admin": true } // 3. Signature // 根据alg算法与私有秘钥进行加密得到的签名字串; // 这一段是最重要的敏感信息,只能在服务端解密; HMACSHA256(       base64UrlEncode(header) + "." +     base64UrlEncode(payload),     SECREATE_KEY ) 

在使用过程中,服务端通过用户登录验证之后,将Header+Claim信息加密后得到第三段签名,然后将签名返回给客户端,在后续请求中,服务端只需要对用户请求中包含的JWT进行解码,即可验证是否可以授权用户获取相应信息,其原理如下图所示:

JWT

通过比较可以看出,使用JWT可以省去服务端读取Session的步骤,这样更符合RESTful的规范。但是对于客户端(或App端)来说,为了保存用户授权信息,仍然需要通过Cookie或类似的机制进行本地保存。因此JWT是用来取代服务端的Session而非客户端Cookie的方案,当然对于客户端本地存储,HTML5提供了Cookie之外更多的解决方案(localStorage/sessionStorage),究竟采用哪种存储方式,其实从Js操作上来看没有本质上的差异,不同的选择更多是出于安全性的考虑。

2 JWT 安全性

用户授权这样敏感的信息,安全性当然是首先需要考虑的因素。这里主要讨论在使用JWT时如何防止XSS和XSRF两种攻击。

XSS是Web中最常见的一种漏洞(我们的**学报官网就存在这个漏洞这件事我就不说了=.=),其主要原因是对用户输入信息不加过滤,导致用户(被误导)恶意输入的Js代码在访问该网页时被执行,而Js可以读取当前网站域名下保存的Cookie信息。针对这种攻击,无论是Cookie还是localStorage中的信息都有可能被窃取,但防止XSS也相对简单一些,对用户输入的所有信息进行过滤即可。另外,现在越来越多的CDN服务,让我们可以节省服务器流量,但同时也有可能引入不安全的Js脚本,例如前段时间Github被Great Cannon轰击的案例,则需要提高对某度之类服务的警惕。

另外一种更加棘手的XSRF漏洞主要利用Cookie是按照域名存储,同时访问某域名时浏览器会自动携带该域名所保存的Cookie信息这一特征。如果执意要将JWT存储在Cookie中,服务端则需要额外验证请求来源,或者在提交表单中加入随机签名并在处理表单时进行验证。

我在后面的实例中采用将JWT保存在localStorage中的方案,请求时将JWT放入Request Header中的Authorization位。对JWT安全性问题想要了解更多可以参考下面几篇文章:

  1. Where to Store Your JWTs - Cookies vs HTML5 Web Storage
  2. Use JWT the Right Way!
  3. 10 Things You Should Know about Tokens
  4. Where to store JWT in browser? How to protect against CSRF?

3 React-jwt Example

本节源码可见Github: react-jwt-example

前面提到的React.js框架学习成本其实非常低,只要跟着官方教程走一遍,搞清楚props、states、virtual DOM几个概念,就可以开始用了。但是只有View层什么都做不了,Facebook推出配套的Flux架构,一开始看到下面这张架构图,当时我就懵逼了。

Flux diagram

好在Flux只是一种理论架构,虽然官方也提供了实现方案,但是我更倾向于Reflux.js的实现方式,如下图所示:

Reflux.js

其中View Components即视图层由React负责,Stores用于存储数据,Actions则用于监听所有动作,所有数据的传递都是单向绑定的,在分割不同模块时,可以清楚地看到数据的流动方向。

我尝试写了一个简单的登录、登出以及获取用户个人数据的例子,除了Reflux之外,还用到如下模块:

  1. react-router: SPA路由;
  2. react-bootstrap: React化的Bootstrap,UI样式;
  3. reqwest: Ajax请求;
  4. jwt-decode: 客户端的JWT解码;

另外服务端API采用Go gin框架,依赖于jwt-go。代码目录结构如下:

tree -I 'node_modules|.git'   . ├── README.md ├── gulpfile.js ├── index.html ├── package.json ├── scripts │ ├── actions │ │ └── actions.js │ ├── app.js │ ├── build │ │ └── dist.js │ ├── components │ │ └── HelloWorld.js │ ├── stores │ │ ├── loginStore.js │ │ └── userStore.js │ └── views │     ├── home.js │     ├── login.js │     └── profile.js └── server.go 

完整的页面放在view中,可复用的组件放在components,用户的动作包括login、logout以及getBalance,因此需要创建相应的action来监听这些动作:

// actions.js var actions = Reflux.createActions({     "login": {},   "updateProfile": {}, // login成功更新用户数据   "loginError": {}, // login失败错误信息   "logout": {},   "getBalance": {asyncResult: true} });  actions.login.listen(function(data){});   

用户点击view中的Submit Button时,将表单信息提交给login action:

// views/login.js var Login = React.createClass({     ...   login: function (e) {     e.preventDefault();     actions.login({       name: this.refs.name.getValue(),       pass: this.refs.pass.getValue(),     }),   ... }); // actions.js var req    = require('reqwest');   actions.login.listen(function(data){     req({     url: HOST+"/user/token",     method: "post",     data: JSON.stringify(data),     type: 'json',     contentType: 'application/json',     headers: {'X-Requested-With': 'XMLHttpRequest'},     success: function (resp) {       if(resp.code == 200){         actions.updateProfile(resp.jwt)       }else{         actions.updateProfile(resp.msg)       }     },   }) }); 

根据API返回结果,将再次触发updateProfile或updateProfile action,而分别由userStore和loginStore接收:

<iframe id="aswift_1" style="left: 0px; position: absolute; top: 0px;" name="aswift_1" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="336" height="280"></iframe>
// stores/userStore.js var userStore = Reflux.createStore({     listenables: actions, // 声明userStore所监听的action   updateProfile: function(jwt){     // 注册监听actions.updateProfile     localStorage.setItem('jwt', jwt);     this.user = jwt_decode(jwt);     this.user.logd = true;     this.trigger(this.user);   }, }) // stores/loginStore.js var loginStore = Reflux.createStore({     listenables: actions,   loginError: function(msg){     this.trigger(msg);   }, }); 

store接收action数据后,通过this.trigger(msg)将处理过后的数据重新传递会view:

var Login = React.createClass({     mixins : [     Router.Navigation,     Reflux.listenTo(userStore, 'onLoginSucc'),     Reflux.listenTo(loginStore, 'onLoginErr')   ],   onLoginSucc: function(){     // 登录成功,跳转回首页     this.transitionTo('home');   },   onLoginErr: function (msg) {     // 登录失败,显示错误信息     this.setState({       errorMsg: msg,      });   },   ... }); 

至此,从用户点击登录到登录结果传回,整个流程数据在View->Action->Store->View中完成单向传递,这就是Flux架构的基本概念。

在完成登录后,API会将验证通过的JWT传回:

// server.go token := jwt.New(jwt.SigningMethodHS256)   // Headers token.Header["alg"] = "HS256"   token.Header["typ"] = "JWT"   // Claims token.Claims["name"] = validUser.Name   token.Claims["mail"] = validUser.Mail   token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()   tokenString, err := token.SignedString([]byte(mySigningKey))   if err != nil {     c.JSON(200, gin.H{"code": 500, "msg": "Server error!"})   return } c.JSON(200, gin.H{"code": 200, "msg": "OK", "jwt": tokenString})   

当登录之后的用户在profile页面发起getBalance请求时,存储于本地的jwt将一起传递,我这里采用Header的方式传递,具体取决于API端的协议:

// actions.js actions.getBalance.listen(function(){     var jwt = localStorage.getItem('jwt');   req({     url: HOST+"/user/balance",     method: "post",     type: "json",     headers: {       'Authorization': "Bearer "+jwt,     },     success: function (resp) {       if (resp.code == 200) {         actions.updateProfile(resp.jwt);       }else{         actions.loginError(resp.msg);       }     }   }) }) 

而服务端面对任何需要验证权限的请求需要通过Token验证:

//server.go token, err := jwt.ParseFromRequest(c.Request, func(token *jwt.Token) (interface{}, error) {     b := ([]byte(mySigningKey))   return b, nil })
  • 大小: 392.3 KB
分享到:
评论

相关推荐

    基于改进YOLOv5s的森林烟火检测算法.pdf

    基于改进YOLOv5s的森林烟火检测算法.pdf

    人力资源管理工具绩效考核excel模板01.xlsx

    人力资源管理工具绩效考核excel模板01

    施工班组长绩效考核表.xls

    施工班组长绩效考核表

    57 -营业部经理绩效考核表1.xlsx

    57 -营业部经理绩效考核表1

    XX公司行政部绩效考核指标.xls

    XX公司行政部绩效考核指标

    ant-apache-xalan2-1.9.4-2.el7.x64-86.rpm.tar.gz

    1、文件内容:ant-apache-xalan2-1.9.4-2.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ant-apache-xalan2-1.9.4-2.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装

    部门绩效考核表模板(基于KPI以月度为例2).xlsx

    部门绩效考核表模板(基于KPI以月度为例2)

    11-6-质检员绩效考核表(含自动计算、等级评价及任意设置等级).xlsx

    11-6-质检员绩效考核表(含自动计算、等级评价及任意设置等级)

    2024年最新全国河流、湖泊矢量数据(数据权威)

    2024最新全国河流湖泊矢量数据 【数据介绍】 2024年中国河流湖泊数据 一份包含中国境内所有主要河流和湖泊的地理信息数据。 数据格式:Shapefile:广泛使用的GIS数据格式,方便在各类GIS软件中使用。 数据获取:访问OpenStreetMap官网,通过导出工具选择中国区域并下载所需的数据。 使用Geofabrik等第三方网站,可以下载预处理好的中国区域的OSM数据。 数据使用:GIS软件:如QGIS、ArcGIS等,用户可以在这些软件中导入OSM数据进行可视化、分析和编辑。 数据应用: 环境研究:分析河流湖泊的水质变化,研究水资源分布及其环境影响。 城市规划:用于规划城市水系、洪水防控、水资源管理等。 导航和旅游:为河流湖泊的导航和旅游路线规划提供数据支持。 科研:为水文地理研究、生态保护、气候变化等领域提供基础数据。 数据特点: 实时更新:OSM数据由全球用户贡献,具有较高的实时性和更新频率。 开放性:所有数据都在开放许可下发布,允许用户自由使用、修改和分发。 详细性:由于全球志愿者的不断努力,数据细节较为丰富,涵盖了从主要河流湖泊到小型水体的广泛范围。 数据时间2024年5月,shp格式,数据来源OpenStreetMap。 OpenStreetMap(OSM)介绍: 一个开放的、免费的、全球性的地图项目,由全球的志愿者和地图爱好者们共同创建和维护。 OSM的数据包括道路、建筑、公园、河流、湖泊等各类地理信息。由于是由众多志愿者共同编辑,OSM的数据具有很高的实时性和详细程度,特别是在一些活跃的区域,地图数据的更新速度和精度往往超过商业地图服务。 用户可以直接在OSM官网下载地图数据,数据格式主要有OSM XML和PBF等。此外,还有一些第三方网站和工具提供更加便捷的数据下载和处理服务,如Geofabrik、Overpass API等。 OSM的数据可以在各种GIS软件中使用,如QGIS、ArcGIS等。此外,还可以使用Python的OSMnx、GeoPandas等库进行编程处理,或者通过Leaflet、Mapbox等JavaScript库将OSM数据集成到web地图应用中。 OSM的所有数据都在开放许可下发布,允许用户自由使用、修改和分发。这使得OSM成为了许多公共项目、研究机构和商业公司的重要数据来源。

    部门绩效考核评分表.xlsx

    部门绩效考核评分表

    12-11-运输车队长绩效考核表(含自动计算、等级评价).xlsx

    12-11-运输车队长绩效考核表(含自动计算、等级评价)

    ant-javadoc-1.9.4-2.el7.x64-86.rpm.tar.gz

    1、文件内容:ant-javadoc-1.9.4-2.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ant-javadoc-1.9.4-2.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装

    springboot整合 freemarker方法

    springboot整合 freemarker方法

    apache-commons-codec-1.8-7.el7.x64-86.rpm.tar.gz

    1、文件内容:apache-commons-codec-1.8-7.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/apache-commons-codec-1.8-7.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装

    (数据权威)全国旅游抽样调查数据

    《旅游抽样调查资料》是反映入境游客在华(内地)花费和国内居民国内旅游情况的资料性年刊,分为上下两篇。 上篇为在华(内地)停留时间在3个月以内的入境游客抽样调查资料,由综合分析报告和调查分类数据两部分组成,分类数据包括:入境游客的主要特征,入境外国人、港澳台同胞的花费水平和花费构成、在境内的停留时间以及入境次数、流向和对住宿单位的选择等。 下篇为国内旅游抽样调查资料,汇集了对城镇居民和农村居民的国内旅游抽样调查结果,共分为四个部分:第一部分为综合分析报告;第二部分为国内旅游出游及花费情况;第三部分为城镇居民国内旅游抽样调查分类数据;第四部分为农村居民国内旅游抽样调查分类数据。

    二代身份证信息读取(vfp8.0)

    1、表单界面,身份证信息保存在dbf表中,供vfp应用使用,可导出为xls电子表格。 2、提供了身份证过期校验和查询功能。

    人事行政主管绩效考核评分表.xls

    人事行政主管绩效考核评分表

    08 -大堂副理绩效考核表1.xlsx

    08 -大堂副理绩效考核表1

    apr-1.4.8-7.el7.x64-86.rpm.tar.gz

    1、文件内容:apr-1.4.8-7.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/apr-1.4.8-7.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装

    ComponentNameError解决办法.md

    ComponentNameError解决办法.md

Global site tag (gtag.js) - Google Analytics