|
| 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+08:00" /> |
| 41 | +<meta itemprop="dateModified" content="2020-01-17T10:39:00+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 => { |
| 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 => { |
| 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> && 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: 'access_token过期,自动续期', 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> && 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: 'refresh_token过期,重定向登录', code = 50015 refresh_token 过期 |
| 152 | +</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> console.log(<span style="color:#0086d2">'refresh_token过期 超时......'</span>) |
| 153 | + MessageBox.confirm(<span style="color:#0086d2">'你已被登出,可以取消继续留在该页面,或者重新登录'</span>, <span style="color:#0086d2">'确定登出'</span>, { |
| 154 | + confirmButtonText: <span style="color:#0086d2">'重新登录'</span>, |
| 155 | + cancelButtonText: <span style="color:#0086d2">'取消'</span>, |
| 156 | + type: <span style="color:#0086d2">'warning'</span> |
| 157 | + }).then(() => { |
| 158 | + store.dispatch(<span style="color:#0086d2">'FedLogOut'</span>).then(() => { |
| 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">'handleCheckRefreshToken'</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">'X-Token'</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 => { |
| 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-- ['X-Token']为自定义key 请根据实际情况自行修改 |
| 185 | +</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> config.headers[<span style="color:#0086d2">'X-Token'</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">'/'</span>).pop() === <span style="color:#0086d2">'refreshtoken'</span>) { |
| 191 | + <span style="color:#080;background-color:#0f140f;font-style:italic">// console.log('config.url', config.url, getRefreshToken()) |
| 192 | +</span><span style="color:#080;background-color:#0f140f;font-style:italic"></span> config.headers[<span style="color:#0086d2">'X-Token'</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 => { |
| 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