首頁 > 軟體

無感知重新整理Token範例簡析

2023-04-08 06:01:12

引言

在前後端分離的應用中,使用Token進行認證是一種較為常見的方式。但是,由於Token的有效期限制,需要不斷重新整理Token,否則會導致使用者認證失敗。為了解決這個問題,可以實現無感知重新整理Token的功能,本文將介紹如何實現無感知重新整理Token。

Token認證的原理

在Web應用中,常見的Token認證方式有基於Cookie和基於Token的認證。基於Cookie的認證方式是將認證資訊儲存在Cookie中,每次請求時將Cookie傳送給伺服器進行認證;而基於Token的認證方式是將認證資訊儲存在Token中,每次請求時將Token傳送給伺服器進行認證。

在基於Token的認證方式中,使用者端將認證資訊儲存在Token中,而不是儲存在Cookie中。在認證成功後,伺服器將生成一個Access Token和一個Refresh Token,並將它們返回給使用者端。Access Token用於存取受保護的API,Refresh Token用於獲取新的Access Token。

什麼是無感知重新整理Token

無感知重新整理Token是指,在Token過期之前,系統自動使用Refresh Token獲取新的Access Token,從而實現Token的無感知重新整理,使用者可以無縫繼續使用應用。

在實現無感知重新整理Token的過程中,需要考慮以下幾個方面:

  • 如何判斷Token是否過期?
  • 如何在Token過期時自動使用Refresh Token獲取新的Access Token?
  • 如何處理Refresh Token的安全問題?

下面將介紹如何實現無感知重新整理Token的具體步驟。

實現步驟

步驟一:獲取Access Token和Refresh Token

在認證成功後,需要將Access Token和Refresh Token傳送給使用者端。Access Token用於存取受保護的API,Refresh Token用於獲取新的Access Token。可以使用JWT(JSON Web Token)或OAuth2(開放授權)等方式實現認證。

在JWT中,可以使用如下程式碼生成Access Token和Refresh Token:

const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
const refreshToken = jwt.sign({userId: '123'}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'});

步驟二:在請求中攜帶Access Token

在每個需要認證的API請求中,需要在請求頭中攜帶Access Token,如下所示:

GET /api/user HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

在前端中,可以使用Axios等庫設定請求頭:

axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;

步驟三:攔截401 Unauthorized響應

在伺服器返回401 Unauthorized響應時,說明Access Token已經過期,需要使用Refresh Token獲取新的Access Token。可以使用Axios攔截器或Fetch API的中介軟體實現攔截。

在Axios中,可以使用如下程式碼實現攔截器:

axios.interceptors.response.use(response => {
  return response;
}, error => {
  const originalRequest = error.config;
  if (error.response.status === 401 && !originalRequest._retry) {
    originalRequest._retry = true; //防止無限呼叫
    return axios.post('/api/refresh_token', {refreshToken})
      .then(response => {
        const { access_token, refresh_token } = response.data;
        localStorage.setItem('access_token', access_token);
        localStorage.setItem('refresh_token', refresh_token);
        axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
        originalRequest.headers.Authorization = `Bearer ${access_token}`;
        return axios(originalRequest);
      });
  }
  return Promise.reject(error);
});

在Fetch中,可以使用如下程式碼實現中介軟體:

function authMiddleware(request) {
  const access_token = localStorage.getItem('access_token');
  if (access_token) {
    request.headers.set('Authorization', `Bearer ${access_token}`);
  }
  return request;
}
function tokenRefreshMiddleware(response) {
  if (response.status === 401) {
    const refreshToken = localStorage.getItem('refresh_token');
    return fetch('/api/refresh_token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ refreshToken })
    }).then(response => {
      if (response.ok) {
        return response.json();
      }
      throw new Error('Refresh Token failed');
    }).then(data => {
      localStorage.setItem('access_token', data.access_token);
      localStorage.setItem('refresh_token', data.refresh_token);
      return Promise.resolve('refreshed');
    }).catch(error => {
      localStorage.removeItem('access_token');
      localStorage.removeItem('refresh_token');
      return Promise.reject(error);
    });
  }
  return Promise.resolve('ok');
}
fetch('/api/user', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  },
  middleware: [authMiddleware, tokenRefreshMiddleware]
}).then(response => {
  console.log(response);
}).catch(error => {
  console.error(error);
});

在上述程式碼中,使用Axios或Fetch攔截器攔截401 Unauthorized響應,如果發現Access Token已經過期,則傳送Refresh Token請求獲取新的Access Token,並將新的Access Token設定到請求頭中,重新傳送請求。

步驟四:伺服器處理Refresh Token請求

在伺服器端,需要編寫API處理Refresh Token請求,生成新的Access Token,並返回給使用者端。

JWT中,可以使用如下程式碼生成新的Access Token:

const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});

在重新整理Token時,需要驗證Refresh Token的合法性,可以使用如下程式碼驗證Refresh Token:

try {
  const payload = jwt.verify(refreshToken, 'REFRESH_TOKEN_SECRET');
  const accessToken = jwt.sign({userId: payload.userId}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
  const refreshToken = jwt.sign({userId: payload.userId}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'});
  res.json({access_token: accessToken, refresh_token: refreshToken});
} catch (err) {
  res.sendStatus(401);
}

在上述程式碼中,使用JWT的verify方法驗證Refresh Token的合法性,如果驗證成功,則生成新的Access Token和Refresh Token,並返回給使用者端。

步驟五:設定定時重新整理Token

為了避免Access Token過期時間太長,可以設定定時重新整理Token的功能。可以使用定時器或Web Workers等方式實現定時重新整理Token。在每次重新整理Token時,需要重新獲取新的Access Token和Refresh Token,並儲存到使用者端。

function refreshToken() {
  const refreshToken = localStorage.getItem('refresh_token');
  axios.post('/api/refresh_token', {refreshToken})
    .then(response => {
      const { access_token, refresh_token } = response.data;
      localStorage.setItem('access_token', access_token);
      localStorage.setItem('refresh_token', refresh_token);
      axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
    })
    .catch(error => {
      console.error(error);
    });
}
setInterval(refreshToken, 14 * 60 * 1000); // 每14分鐘重新整理Token

在上述程式碼中,使用定時器每14分鐘重新整理Token。在重新整理Token成功後,將新的Access Token和Refresh Token儲存到使用者端,並將新的Access Token設定到請求頭中。

安全性考慮

在實現無感知重新整理Token的過程中,需要考慮到Refresh Token的安全性問題。因為Refresh Token具有長期的有效期限,一旦Refresh Token被洩露,攻擊者就可以使用Refresh Token獲取新的Access Token,從而繞過認證機制,存取受保護的API。

為了增加Refresh Token的安全性,可以考慮以下幾種措施:

  • 將Refresh Token儲存在HttpOnly Cookie中,可以避免在使用者端被JavaScript獲取;
  • 對Refresh Token進行加密或簽名,可以增加其安全性。
  • 將Refresh Token儲存在後端,前端通過介面和後端互動,實現重新整理Access Token。

以上就是無感知重新整理Token的詳細內容,更多關於無感知重新整理Token的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com