前后端分离详解
本⽂⽬录
⼀传统的开发模式
前后端分离前我们的开发协作模式⼀般是这样的:
前端写好静态的HTML页⾯交付给后端开发。静态页⾯可以本地开发,也⽆需考虑业务逻辑只需要实现View即可。
后端使⽤模板引擎去套模板,同时内嵌⼀些后端提供的模板变量和⼀些逻辑操作。
然后前后端集成对接,遇到问题,前台返⼯,后台返⼯。
然后在集成,直⾄集成成功。
这种模式的问题
在前端调试的时候要安装完整的⼀套后端开发⼯具,要把后端程序完全启动起来。遇到问题需要后端开发来帮忙调试。我们发现前后端严重耦合,还要要求后端⼈员会⼀些HTML,JS等前端语⾔。前端页⾯⾥还
嵌⼊了很多后端的代码。⼀旦后端换了⼀种语⾔开发,简直就要重做。
像这种增加了⼤量的沟通成本,调试成本等,⽽且前后端的开发进度相互影响,从⽽⼤⼤降低了开发效率。
⼆前后端分离的开发模式
前后端分离并不只是开发模式,⽽是web应⽤的⼀种架构模式。在开发阶段,前后端⼯程师约定好数据交互接⼝,实现并⾏开发和测试;在运⾏阶段前后端分离模式需要对web应⽤进⾏分离部署,前后端之前使⽤HTTP或者其他协议进⾏交互请求。
1. 客户端和服务端采⽤RESTFul API的交互⽅式进⾏交互
2. 前后端代码库分离
在传统架构模式中,前后端代码存放于同⼀个代码库中,甚⾄是同⼀⼯程⽬录下。页⾯中还夹杂着后端代码。前后端⼯程师进⾏开发时,都必须把整个项⽬导⼊到开发⼯具中。
前后端代码库分离,前端代码中有可以进⾏Mock测试(通过构造虚拟测试对象以简化测试环境的⽅法)的伪后端,能⽀持前端的独⽴开发和测试。⽽后端代码中除了功能实现外,还有着详细的测试⽤例,以保证API的可⽤性,降低集成风险。
3. 并⾏开发
在开发期间前后端共同商定好数据接⼝的交互形式和数据格式。然后实现前后端的并⾏开发,其中前端⼯程师在开发完成之后可以独⾃进⾏mock测试,⽽后端也可以使⽤Postman等接⼝测试软件进⾏接⼝⾃测,然后前后端⼀起进⾏功能联调并校验格式,最终进⾏⾃动化测试。
分离后,开发模式是这样的
三前后分离的优点
为优质产品打造精益团队
通过将开发团队前后端分离化,让前后端⼯程师只需要专注于前端或后端的开发⼯作,是的前后端⼯程师实现⾃治,培养其独特的技术特性,然后构建出⼀个全栈式的精益开发团队。
提升开发效率
前后端分离以后,可以实现前后端代码的解耦,只要前后端沟通约定好应⽤所需接⼝以及接⼝参数,便可以开始并⾏开发,⽆需等待对⽅的开发⼯作结束。与此同时,即使需求发⽣变更,只要接⼝与数据格式不变,后端开发⼈员就不需要修改代码,只要前端进⾏变动即可。如此⼀来整个应⽤的开发效率必然会有质的提升。
完美应对复杂多变的前端需求
如果开发团队能完成前后端分离的转型,打造优秀的前后端团队,开发独⽴化,让开发⼈员做到专注专精,开发能⼒必然会有所提升,能够完美应对各种复杂多变的前端需求。
增强代码可维护性
前后端分离后,应⽤的代码不再是前后端混合,只有在运⾏期才会有调⽤依赖关系。应⽤代码将会变得整洁清晰,不论是代码阅读还是代码维护都会⽐以前轻松。
使⽤了前后端分离架构后,除了开发模式的变更,我们在开发的过程中还会遇到哪些问题呢?我们接着往下看。
四 JWT实现⽤户认证
我们先来看看传统开发,我们是如何进⾏⽤户认证的
前端登录,后端根据⽤户信息⽣成⼀个jsessionid,并保存这个 jsessionid 和对应的⽤户id到Session中,接着把 jsessionid 传给⽤户,存⼊浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询⽤户,验证是否过期。
HTTP有⼀个特性:⽆状态的,就是前后两个HTTP事务它们并不知道对⽅的信息。⽽为了维护会话信息或⽤户信息,⼀般可⽤Cookie和Session技术缓存信息。
- Cookie是存储在客户端的
- Session是存储在服务端的
但这样做问题就很多,如果我们的页⾯出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致⽤户 token 泄露,⽽作为后端识别⽤户的标识,cookie 的泄露意味着⽤户信息不再安全。尽管我们通过转义输出内容,使⽤ CDN 等可以尽量避免 XSS 注⼊,但谁也不能保证在⼤型的项⽬中不会出现这个问题。
在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会⾃动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉⼀些使⽤ HTTP 协议的 XSS 注⼊,但并不能完全阻⽌。
httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注⼊的问题也基本不⽤担⼼了。但设置 httpOnly 就带来了另⼀个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页⾯的时候,另⼀个页⾯可以很容易的跨站请求这个页⾯的内容。因为 cookie 默认被发了出去。
解决⽅案
JWT(Json Web Token)
JWT 是⼀个开放标准(RFC 7519),它定义了⼀种⽤于简洁,⾃包含的⽤于通信双⽅之间以 JSON 对象的形式安全传递信息的⽅法。该信息可以被验证和信任,因为它是数字签名的。JWTS可以使⽤秘密(使⽤HMAC算法)或公钥/私钥对使⽤RSA或ECDSA来签名。
- 简洁(Compact):可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量⼩,传输速度快。
- ⾃包含(Self-contained):负载中包含了所有⽤户所需要的信息,避免了多次查询数据库。
JWT 组成
JWT由3个⼦字符串组成,分别为Header,Payload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json 的信息的⼀个Json,将Claim转码之后⽣成Payload)。
Header
前端测试和后端测试的区别
Header是由下⾯这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的⽅式获取到这个原来的Json,所以JWT中存放的⼀般是不敏感的信息)⽣成的字符串,Header中存放的内容是说明编码对象是⼀个JWT以及使⽤“SHA-256”的算法进⾏加密(加密⽤于⽣成Signature)
{
"typ":"JWT",
"alg":"HS256"
}
- 头部包含了两部分,token 类型和采⽤的加密算法
- Base64是⼀种编码,也就是说,它是可以被翻译回原来的样⼦来的。它并不是⼀种加密过程。
Payload
Payload是通过Claim进⾏Base64转码之后⽣成的⼀串字符串,Claim是⼀个Json,Claim中存放的内容是JWT⾃⾝的标准属性,所有的标准属性都是可选的,可以⾃⾏添加,⽐如:JWT的签发者、JWT的接
收者、JWT的持续时间等;同时Claim中也可以存放⼀些⾃定义的属性,这个⾃定义的属性就是在⽤户认证中⽤于标明⽤户⾝份的⼀个属性,⽐如⽤户存放在数据库中的id,为了安全起见,⼀般不会将⽤户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后⽣成的⼀串字符串称作Payload。
{
"iss":"Issuer —— ⽤于说明该JWT是由谁签发的",
"sub":"Subject —— ⽤于说明该JWT⾯向的对象",
"aud":"Audience —— ⽤于说明该JWT发送给的⽤户",
"exp":"Expiration Time —— 数字类型,说明该JWT过期的时间",
"nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理",
"iat":"Issued At —— 数字类型,说明该JWT何时被签发",
"jti":"JWT ID —— 说明标明JWT的唯⼀ID",
"user-definde1":"⾃定义属性举例",
"user-definde2":"⾃定义属性举例"
}
Signature
Signature是由Header和Payload组合⽽成,将Header和Claim这两个Json分别使⽤Base64⽅式进⾏编码,⽣成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在⼀起形成⼀个字符串,然后使⽤上⾯定义好的加密算法和⼀个密匙(这个密匙存放在服务器上,⽤于进⾏验证)对这个字符串进⾏加密,形成⼀个新的字符串,这个字符串就是Signature。
签名的⽬的:最后⼀步签名的过程,实际上是对头部以及负载内容进⾏签名,防⽌内容被窜改。如果有⼈对头部以及负载的内容解码之后进⾏修改,再进⾏编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不⼀样的。如果要对新的头部和负载进⾏签名,在不知道服务器加密时⽤的密钥的话,得出来的签名也是不⼀样的。
三个部分通过.连接在⼀起就是我们的 JWT 了:
JWT认证
服务器在⽣成⼀个JWT之后会将这个token发送到客户端机器,在客户端再次访问受到JWT保护的资源URL链接的时候,服务器会获取到这个token信息,⾸先将Header进⾏反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进⾏加密,⽐对token中的Signature和实际加密出来的结果是否⼀致,如果⼀致那么说明该token是合法有效的,认证成功,否则认证失败。
JWT使⽤总结
1. ⾸先,前端通过Web表单将⾃⼰的⽤户名和密码发送到后端的接⼝。这⼀过程⼀般是⼀个HTTP POST请求。建议的⽅式是通过SSL加密的传输(https协议),从⽽避免敏感信息被嗅探。
2. 后端核对⽤户名和密码成功后,将⽤户的id等其他信息作为JWT Payload(负载),将其与头部分别进⾏Base64编码拼接后签名,形成⼀个JWT。形成的JWT就是⼀个形同的字符串。
3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在Cookie或localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
4. 前端在每次请求时将JWT放⼊HTTP Header中的Authorization位。(解决XSS和XSRF问题)
5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收⽅是否是⾃⼰(可选)。
6. 验证通过后后端使⽤JWT中包含的⽤户信息进⾏其他逻辑操作,返回相应结果。
JWT和Session⽅式存储id的差异
Session⽅式存储⽤户id的最⼤弊病在于Session是存储在服务器端的,所以需要占⽤⼤量服务器内存,对于较⼤型应⽤⽽⾔可能还要保存许多的状态。⼀般⽽⾔,⼤型应⽤还需要借助⼀些KV数据库和⼀系列缓存机制来实现Session的存储。
⽽JWT⽅式将⽤户状态分散到了客户端中,可以明显减轻服务端的内存压⼒。除了⽤户id之外,还可以存储其他的和⽤户相关的信息,例如该⽤户是否是管理员、⽤户所在的分组等。虽说JWT⽅式让服务器有⼀些计算压⼒(例如加密、编码和解码),但是这些压⼒相⽐磁盘存储⽽⾔可能就不算什么了。
单点登录
Session⽅式来存储⽤户id,⼀开始⽤户的Session只会存储在⼀台服务器上。对于有多个⼦域名的站点,每个⼦域名⾄少会对应⼀台不同的服务器,例如:
www.taobao,nv.taobao,nz.taobao,login.taobao。所以如果要实现在login.taobao登录后,在其他的⼦域名下依然可以取到Session,这要求我们在多台服务器上同步Session。使⽤JWT的⽅式则没有这个问题的存在,因为⽤户的状态已经被传送到了客户端。
五跨域问题解决
当客户端和服务端分开部署到不同服务器的时候,就会遇到跨域访问的问题,是由浏览器同源策略限制的⼀类请求场景。
跨域解决⽅案有很多种,下⾯使⽤Nginx反向代理的⽅案
反向代理
代理访问其实在实际应⽤中有很多场景,在跨域中应⽤的原理做法为:通过反向代理服务器监听同端⼝,同域名的访问,不同路径映射到不同的地址,⽐如,在nginx服务器中,监听同⼀个域名和端⼝,不同路径转发到客户端和服务器,把不同端⼝和域名的限制通过反向代理,来解决跨域的问题:
server {
listen 80;
server_name domain;
#charset koi8-r;
#access_log logs/host.access.log main;
location /client { #访问客户端路径
proxy_pass localhost:81;
proxy_redirect default;
}
location /apis { #访问服务器路径
rewrite ^/apis/(.*)$ /$1 break;
proxy_pass localhost:82;
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论