Skip to content

Commit 2e181b3

Browse files
committed
fixed
1 parent 186ae27 commit 2e181b3

File tree

7 files changed

+919
-0
lines changed

7 files changed

+919
-0
lines changed

posts/vue-token-guo-qi-wu-feng-shua-xin/index.html

+301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
<!DOCTYPE html>
2+
<html lang="zh">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="author" content="pocoyo" />
7+
<meta name="description" content="Personal blog." />
8+
<meta name="keywords" content="blog, tech" />
9+
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
10+
<meta name="generator" content="Hugo 0.62.2" />
11+
12+
<link rel="canonical" href="/posts/vue-token-guo-qi-wu-feng-shua-xin/">
13+
<link rel="icon" href="https://emacle.github.io/favicon.ico">
14+
<meta property="og:title" content="vue token过期无缝刷新" />
15+
<meta property="og:description" content="思路:
16+
登录时, 后端生成 access_token, refresh_token 返回前端, 前端保存两个token在 cookie或localstorge中
17+
当前端发送正常请求时,请求头字段携带 access_token , 后端提取该 access_token
18+
判断是否过期, 不过期则返回 HTTP 200 OK 过期返回 HTTP_UNAUTHORIZED 401, 并且加上自定义响应数据 code = 50014 表示access_token 过期 VUE前端使用 响应拦截器 , 对收到的 HTTP 401 进行拦截, 如果 http 401 且 code =50014 则先以 refresh_token 去获取新 access_token
19+
如果正常获得 access_token, 则再次以新 access_token 发送原请求, 即可实现无缝刷新 如果 refresh_token 也过期, 则服务器也返回 401, 但是加上了自定义响应数据 code= 50015, 前端的响应拦截器 再次捕获到 error , 校验code =50015后, 则强制退出需要重新登录 // response interceptorservice." />
20+
<meta property="og:type" content="article" />
21+
<meta property="og:url" content="/posts/vue-token-guo-qi-wu-feng-shua-xin/" />
22+
<meta property="article:published_time" content="2020-01-17T10:39:00+08:00" />
23+
<meta property="article:modified_time" content="2020-01-17T10:39:00+08:00" />
24+
25+
<meta name="twitter:card" content="summary"/>
26+
<meta name="twitter:title" content="vue token过期无缝刷新"/>
27+
<meta name="twitter:description" content="思路:
28+
登录时, 后端生成 access_token, refresh_token 返回前端, 前端保存两个token在 cookie或localstorge中
29+
当前端发送正常请求时,请求头字段携带 access_token , 后端提取该 access_token
30+
判断是否过期, 不过期则返回 HTTP 200 OK 过期返回 HTTP_UNAUTHORIZED 401, 并且加上自定义响应数据 code = 50014 表示access_token 过期 VUE前端使用 响应拦截器 , 对收到的 HTTP 401 进行拦截, 如果 http 401 且 code =50014 则先以 refresh_token 去获取新 access_token
31+
如果正常获得 access_token, 则再次以新 access_token 发送原请求, 即可实现无缝刷新 如果 refresh_token 也过期, 则服务器也返回 401, 但是加上了自定义响应数据 code= 50015, 前端的响应拦截器 再次捕获到 error , 校验code =50015后, 则强制退出需要重新登录 // response interceptorservice."/>
32+
33+
34+
<meta itemprop="name" content="vue token过期无缝刷新">
35+
<meta itemprop="description" content="思路:
36+
登录时, 后端生成 access_token, refresh_token 返回前端, 前端保存两个token在 cookie或localstorge中
37+
当前端发送正常请求时,请求头字段携带 access_token , 后端提取该 access_token
38+
判断是否过期, 不过期则返回 HTTP 200 OK 过期返回 HTTP_UNAUTHORIZED 401, 并且加上自定义响应数据 code = 50014 表示access_token 过期 VUE前端使用 响应拦截器 , 对收到的 HTTP 401 进行拦截, 如果 http 401 且 code =50014 则先以 refresh_token 去获取新 access_token
39+
如果正常获得 access_token, 则再次以新 access_token 发送原请求, 即可实现无缝刷新 如果 refresh_token 也过期, 则服务器也返回 401, 但是加上了自定义响应数据 code= 50015, 前端的响应拦截器 再次捕获到 error , 校验code =50015后, 则强制退出需要重新登录 // response interceptorservice.">
40+
<meta itemprop="datePublished" content="2020-01-17T10:39:00&#43;08:00" />
41+
<meta itemprop="dateModified" content="2020-01-17T10:39:00&#43;08:00" />
42+
<meta itemprop="wordCount" content="316">
43+
44+
45+
46+
<meta itemprop="keywords" content="vue,jwt,token," />
47+
48+
<link rel="stylesheet" href="/css/layout.css" />
49+
50+
51+
<link rel="stylesheet" href="/css/default-dark.css" />
52+
53+
54+
55+
56+
<title>
57+
58+
59+
vue token过期无缝刷新
60+
61+
</title>
62+
63+
</head>
64+
65+
66+
<body>
67+
<div class="main">
68+
<header>
69+
70+
<div class="header-bar">
71+
72+
<nav>
73+
<div class="siteTitle">
74+
<a href="/">~振鹭于飞~</a>
75+
</div>
76+
77+
78+
79+
<a class="nav-item" href="/posts/"><div class="nav-item-title">Posts</div></a>
80+
81+
<a class="nav-item" href="/tags/"><div class="nav-item-title">Tags</div></a>
82+
83+
84+
</nav>
85+
86+
87+
<div class="social-links-header">
88+
89+
90+
91+
92+
<a href="https://github.com/emacle" target="_blank"><div class="social-link">gh</div></a>
93+
94+
95+
96+
97+
98+
99+
100+
101+
</div>
102+
103+
104+
</div>
105+
106+
107+
</header>
108+
109+
110+
<article class="post">
111+
<h1 class="title"> vue token过期无缝刷新 </h1>
112+
<div class="content"> <p>思路:</p>
113+
<ol>
114+
<li>
115+
<p>登录时, 后端生成 <em>access_token, refresh_token</em> 返回前端, 前端保存两个token在 cookie或localstorge中</p>
116+
</li>
117+
<li>
118+
<p>当前端发送正常请求时,请求头字段携带 <em>access_token</em> , 后端提取该 <em>access_token</em></p>
119+
<ul>
120+
<li>判断是否过期, 不过期则返回 HTTP 200 OK</li>
121+
<li>过期返回 <em>HTTP_UNAUTHORIZED</em> 401, 并且加上自定义响应数据 code = 50014 表示access_token 过期</li>
122+
</ul>
123+
</li>
124+
<li>
125+
<p>VUE前端使用 <strong>响应拦截器</strong> , 对收到的 HTTP 401 进行拦截, 如果 http 401 且 code =50014 则先以 <em>refresh_token</em>
126+
去获取新 <em>access_token</em></p>
127+
<ul>
128+
<li>如果正常获得 access_token, 则再次以新 access_token 发送原请求, 即可实现无缝刷新</li>
129+
<li>如果 <em>refresh_token</em> 也过期, 则服务器也返回 401, 但是加上了自定义响应数据 code= 50015, 前端的响应拦截器
130+
再次捕获到 error , 校验code =50015后, 则强制退出需要重新登录</li>
131+
</ul>
132+
<!-- raw HTML omitted -->
133+
<div class="highlight"><pre style="color:#fff;background-color:#111;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-js" data-lang="js"><span style="color:#080;background-color:#0f140f;font-style:italic">// response interceptor
134+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span>service.interceptors.response.use(
135+
response =&gt; {
136+
<span style="color:#fb660a;font-weight:bold">const</span> res = response.data
137+
<span style="color:#080;background-color:#0f140f;font-style:italic">// 一些处理...
138+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">return</span> response.data
139+
},
140+
error =&gt; {
141+
<span style="color:#080;background-color:#0f140f;font-style:italic">// http 401 只能在 error 里被截获
142+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#080;background-color:#0f140f;font-style:italic">// console.log(error) *** 控制台不能输出返回的响应数据 ***
143+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#080;background-color:#0f140f;font-style:italic">// console.log(error.response) *** 可使用此命令进行调试 ***
144+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">if</span> (error.response.status === <span style="color:#0086f7;font-weight:bold">401</span> &amp;&amp; error.response.data.code === <span style="color:#0086f7;font-weight:bold">50014</span>) {
145+
<span style="color:#080;background-color:#0f140f;font-style:italic">// message: &#39;access_token过期,自动续期&#39;, code = 50014 access_token 过期
146+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">return</span> againRequest(error) <span style="color:#080;background-color:#0f140f;font-style:italic">// 此函数先以refresh_token 去获取新access_token, 然后再次以新 access_token 发送原请求
147+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> }
148+
149+
<span style="color:#080;background-color:#0f140f;font-style:italic">// 这里是 code = 50015 refresh_token 也过期的情况
150+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">if</span> (error.response.status === <span style="color:#0086f7;font-weight:bold">401</span> &amp;&amp; error.response.data.code === <span style="color:#0086f7;font-weight:bold">50015</span>) {
151+
<span style="color:#080;background-color:#0f140f;font-style:italic">// message: &#39;refresh_token过期,重定向登录&#39;, code = 50015 refresh_token 过期
152+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> console.log(<span style="color:#0086d2">&#39;refresh_token过期 超时......&#39;</span>)
153+
MessageBox.confirm(<span style="color:#0086d2">&#39;你已被登出,可以取消继续留在该页面,或者重新登录&#39;</span>, <span style="color:#0086d2">&#39;确定登出&#39;</span>, {
154+
confirmButtonText: <span style="color:#0086d2">&#39;重新登录&#39;</span>,
155+
cancelButtonText: <span style="color:#0086d2">&#39;取消&#39;</span>,
156+
type: <span style="color:#0086d2">&#39;warning&#39;</span>
157+
}).then(() =&gt; {
158+
store.dispatch(<span style="color:#0086d2">&#39;FedLogOut&#39;</span>).then(() =&gt; {
159+
location.reload() <span style="color:#080;background-color:#0f140f;font-style:italic">// 为了重新实例化vue-router对象 避免bug
160+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> })
161+
})
162+
}
163+
164+
<span style="color:#fb660a;font-weight:bold">return</span> Promise.reject(error)
165+
}
166+
) <span style="color:#080;background-color:#0f140f;font-style:italic">// response 拦截结束
167+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span>
168+
async <span style="color:#fb660a;font-weight:bold">function</span> againRequest(error) {
169+
await store.dispatch(<span style="color:#0086d2">&#39;handleCheckRefreshToken&#39;</span>) <span style="color:#080;background-color:#0f140f;font-style:italic">// 同步以获取刷新 access_token 并且保存在 cookie/localstorage
170+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">const</span> config = error.response.config
171+
config.headers[<span style="color:#0086d2">&#39;X-Token&#39;</span>] = getToken() <span style="color:#080;background-color:#0f140f;font-style:italic">// 以新的 access_token
172+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">const</span> res = await axios.request(config) <span style="color:#080;background-color:#0f140f;font-style:italic">// 重新进行原请求
173+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">return</span> res.data <span style="color:#080;background-color:#0f140f;font-style:italic">// 以error.response.config重新请求返回的数据包是在函数内是 被封装在data里面
174+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span>}
175+
</code></pre></div></li>
176+
<li>
177+
<p>第3步以 refresh_token 去获取 access_token 时, 必须在 <strong>请求拦截器</strong> 里重新配置请求头, 以 refresh_token 作为新的 token 头
178+
否则后端token认证判断还是原过期的 access_token</p>
179+
<div class="highlight"><pre style="color:#fff;background-color:#111;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-js" data-lang="js"><span style="color:#080;background-color:#0f140f;font-style:italic">// request interceptor
180+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span>service.interceptors.request.use(
181+
config =&gt; {
182+
<span style="color:#080;background-color:#0f140f;font-style:italic">// Do something before request is sent
183+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">if</span> (store.getters.token) {
184+
<span style="color:#080;background-color:#0f140f;font-style:italic">// 让每个请求携带token-- [&#39;X-Token&#39;]为自定义key 请根据实际情况自行修改
185+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> config.headers[<span style="color:#0086d2">&#39;X-Token&#39;</span>] = getToken()
186+
}
187+
188+
<span style="color:#080;background-color:#0f140f;font-style:italic">// 监听是否 /sys/user/refreshtoken 是则重置token为 refresh_token
189+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> <span style="color:#fb660a;font-weight:bold">const</span> url = config.url
190+
<span style="color:#fb660a;font-weight:bold">if</span> (url.split(<span style="color:#0086d2">&#39;/&#39;</span>).pop() === <span style="color:#0086d2">&#39;refreshtoken&#39;</span>) {
191+
<span style="color:#080;background-color:#0f140f;font-style:italic">// console.log(&#39;config.url&#39;, config.url, getRefreshToken())
192+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> config.headers[<span style="color:#0086d2">&#39;X-Token&#39;</span>] = getRefreshToken() <span style="color:#080;background-color:#0f140f;font-style:italic">// 登录时本地 cookie/localstorage 存储的
193+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> }
194+
<span style="color:#fb660a;font-weight:bold">return</span> config
195+
},
196+
error =&gt; {
197+
<span style="color:#080;background-color:#0f140f;font-style:italic">// Do something with request error
198+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> console.log(error) <span style="color:#080;background-color:#0f140f;font-style:italic">// for debug
199+
</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> Promise.reject(error)
200+
}
201+
)
202+
</code></pre></div><p><img src="https://raw.githubusercontent.com/emacle/vue-php-admin/master/vue-element-admin/static/screenshot/view%5Fjwt.gif" alt="">
203+
<img src="https://raw.githubusercontent.com/emacle/vue-php-admin/master/vue-element-admin/static/screenshot/edit%5Fjwt.gif" alt="">
204+
<img src="https://raw.githubusercontent.com/emacle/vue-php-admin/master/vue-element-admin/static/screenshot/del%5Fjwt.gif" alt=""></p>
205+
<div class="highlight"><pre style="color:#fff;background-color:#111;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">用户编辑时比较有用, 防止长时间编辑后提交时 access_token 过期, 导致编辑内容丢失
206+
</code></pre></div></li>
207+
<li>
208+
<p>完整代码 <a href="https://github.com/emacle/vue-php-admin">https://github.com/emacle/vue-php-admin</a></p>
209+
</li>
210+
<li>
211+
<p>VUE 请求拦截器与响应拦截器代码修改自 <a href="https://github.com/PanJiaChen/vue-element-admin/">vue-element-admin</a> 中的 <a href="https://github.com/PanJiaChen/vue-element-admin/blob/master/src/utils/request.js">request.js</a></p>
212+
</li>
213+
</ol>
214+
<p>参考: <a href="https://blog.csdn.net/cjs5202001/article/details/80228937">php firebase/php-jwt token验证</a></p>
215+
</div>
216+
<footer class="post-footer">
217+
218+
<div class="post-footer-data">
219+
220+
<div class="tags">
221+
222+
223+
<div class="tag">
224+
<a href="/tags/vue">#vue</a>
225+
</div>
226+
227+
<div class="tag">
228+
<a href="/tags/jwt">#jwt</a>
229+
</div>
230+
231+
<div class="tag">
232+
<a href="/tags/token">#token</a>
233+
</div>
234+
235+
236+
</div>
237+
238+
239+
240+
<div class="date"> Fri, 17 Jan 2020 10:39:00 CST </div>
241+
242+
</div>
243+
</footer>
244+
245+
246+
247+
248+
249+
250+
251+
252+
<script src="https://utteranc.es/client.js"
253+
repo="emacle/utterances"
254+
issue-term="pathname"
255+
theme="github-dark"
256+
label="hugo"
257+
crossorigin="anonymous"
258+
async>
259+
</script>
260+
261+
262+
263+
264+
</article>
265+
266+
<footer>
267+
268+
<div class="social-links-footer">
269+
270+
271+
272+
273+
<a href="https://github.com/emacle" target="_blank"><div class="social-link">GitHub</div></a>
274+
275+
276+
277+
278+
279+
280+
281+
282+
<div class="social-link">
283+
<a href="/index.xml" target="_blank">RSS</a>
284+
</div>
285+
286+
</div>
287+
288+
289+
<div class="copyright"> </div>
290+
291+
292+
293+
294+
295+
</footer>
296+
297+
</div>
298+
299+
</body>
300+
</html>
301+

0 commit comments

Comments
 (0)