作為一個全棧ajax的mvvm架構,angularjs可謂如火如荼,可真正做到全棧ajax,首要面對的問題就是使用者身分識別驗證。
本文的身分識別驗證不採用cookie,而採用基於http Authorize 要求標頭的方式驗證使用者,此方式能做到永遠只有一個使用者同時線上(服務端同一時間只會接受一個合法的token請求,其他的請求返回401)
這裡我採用service儲存使用者資訊,service如下
.factory('Authorize', function() { return { uid: '', token: '', logout: function() { this.uid = ''; this.token = ''; localStorage.removeItem('authorize.uid'); localStorage.removeItem('authorize.token'); } } })
由於service是單例的。儲存在service很合適,再看登入檢測。
驗證流程
app運行時主動讀取localStorage中的authorize.uid和authorize.token欄位,將這兩個欄位發送至後端介面驗證,如果驗證成功返回使用者資訊,驗證失敗返回http 401錯誤(未授權)。
如果localStorage沒有上述兩個欄位,則檢測url中是否有,如果有則寫入本地localStorage之後發送至後端驗證,如果沒有,跳轉至後端伺服器的oauth介面進行授權拿之後,將openid和token寫入queryString並回調到app頁面,代碼如下:
Authorize.uid = $location.search().uid || localStorage.getItem('authorize.uid'); Authorize.token = $location.search().token || localStorage.getItem('authorize.token'); if (!Authorize.uid || !Authorize.token) { if (!Platform.isWechat) { Authorize.uid = 1001; Authorize.token = '2ddha3nry8'; } else { location.href = CONFIG.api + '/auth/oauth?callback=' + encodeURIComponent($location.protocol() + "://" + $location.host() + ":" + $location.port() + "/#" + $location.path()); return; } } //寫入本地 localStorage.setItem('authorize.uid', Authorize.uid); localStorage.setItem('authorize.token', Authorize.token); //讀取使用者資料 var user = User.get({uid: Authorize.uid}, function() { $rootScope.user = user; });
讀取使用者資料這邊,採用的$resource服務封裝,這裡就不說了。
請求過程中的授權處理
接下來是比較重要的一點,如何在登陸後在每次要求標頭中注入Authorize資訊,方法是採用攔截器。
有一個問題,如果由於重新整理過快,檢測使用者回調還沒執行完,這時候訪問所有介面都是401,這裡就需要在$httpProvider上注入攔截器進行請求恢複了。代碼如下:
.factory('AuthInjector', function($q, Authorize, $injector, CONFIG) { return { request: function(config) { if (Authorize.token) { config.headers.Authorization = 'Bearer ' + Authorize.token; } return config; }, response: function(response) { var defer = $q.defer(); defer.resolve(response); return defer.promise; }, requestError: function(error) { }, responseError: function(error) { //如果401且本地存在uid,則重新整理accessToken if (error.status == 401) { //重新整理請求 var $http = $injector.get("$http"); $http({ method: 'GET', url: CONFIG.api + '/auth/token', params: { uid: Authorize.uid } }).success(function(data) { Authorize.token = data.token; //寫入本地 localStorage.setItem('authorize.token', data.token); return $http(error.config); }); } else if (error.status == 422) { var resp; angular.forEach(error.data, function(item) { if (resp == undefined) { resp = item; } }); return $q.reject(resp); } return $q.reject({ message: '請求失敗' }); } } })
原理就是所有的http請求一旦返回401就進行重新登入請求,最後一句
return $http(error.config);
大體就是這麼多,總結一下就是
app.run中檢測登入
Authorize服務儲存使用者資訊
httpProvider中注入攔截器實現Authorize頭的自動添加和401結果的請求恢複