课程名称:web前端架构师
课程章节:第16周 第七章 使用 OAuth2 协议完成用户创建以及验证功能
主讲老师:张轩
课程内容: 前后端分离 OAuth 登录、使用 egg-cors 支持跨域
之前实现的 OAuth 都是在服务端实现的,客户端肯定无法使用,前后端分离的项目要使用,就需要做一些其他处理
首先需要先了解下 window.postMessage
. 文档地址 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
postMessage 可以在两个 window 之间传递消息
整个过程是这样的
就这样整个前后端分离授权登录流程就完成了
首先是用户登录页面
<body> <button onclick="giteeOAuth()">gitee 授权</button> </body> <script> // 用户点击授权登录后,跳转到服务端授权页面 B 页面 function giteeOAuth(){ window.open('http://localhost:7001/user/passport/gitee', '__blank') } // 接受 B 页面发送过来的 token window.addEventListener('message', (res) => { console.log(res.data) }) </script>
服务端页面,首先跳转到 gitee 授权页面进行授权
async giteeOAuth() { const { ctx, app } = this; const config = app.config.giteeConfig; const url = `https://gitee.com/oauth/authorize?client_id=${config.cid}&redirect_uri=${config.redirectURL}&response_type=code`; ctx.redirect(url); }
授权成功后,会调用回调接口,回调接口返回一个页面, 将 token 通过 windows.opener.postMessage 发送给用户登录页面
async signByGitee() { const { ctx, service } = this; const { code } = ctx.request.query; const token = await service.user.signByGitee(code); await ctx.render('giteeOAuth.ejs', { token, }); }
这样 gitee 授权登录功能就完成了
浏览器限制脚本内发送跨域 HTTP 请求
何为跨域 Origin?
上面三个中有一个不一样就会出现跨域
// 不同的协议 http://example.com/app1 https://example.com/app2 // 不同的主机 http://example.com http://www.example.com http://myapp.example.com // 不同的端口 http://example.com http://example.com:8080 // 特别注意,下面两个是同源的,因为 http 默认使用的是 80 端口 http://example.com:80 http://example.com
跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
Access-Control-Allow-Origin: <origin> | * // 只能拿到一些最基本的响应头, Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header Access-Control-Allow-Methods: <method>[, <method>]*
npm i egg-cors
开启 egg-cors 插件
// config/plugin.js ... cors: { enable: true, package: 'egg-cors', }, ...
配置 egg-cors 插件
config.cors = { // 允许跨域访问的地址 origin: 'http://localhost:3000', // 允许请求的方式 allowMethods: 'GET,HEAD,PUT,OPTIONS,POST,DELETE,PATCH', };
就这样 egg程序就可以支持跨域了
我们可以在之前授权登录的接口进行请求测试
window.addEventListener('message', (res) => { const token = res.data if (token) { fetch('http://localhost:7001/user', { headers: { Authorization: 'Bearer ' + token } }).then(res => res.json()).then(res => { console.log(res) }) } })
测试可以正常运行
但是,现在我们打开控制台,会看到我们发送两次请求,其中一次请求,它的 type是 Preflight
对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求
简单请求不会发送 Preflight 请求
文档地址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS#简单请求
使用下列方法之一:
Content-Type 的值仅限于下列三者之一
没有自定义的 Header
下面代码会发送两次请求 ,有 preflight。因为没有满足 Content-Type 的值
axios.post('http://localhost:7001/user/signin', { username: 'xxx@qq.com', password: '12345678' }).then(res => { console.log(res) })
修改后,就只会发送一次请求
axios.post('http://localhost:7001/user/signin', { username: 'xxx@qq.com', password: '12345678' }, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(res => { console.log(res) })