我认为更好的方法是在您实际发出请求之前检查令牌。这样您的网络流量更少,响应速度更快。
编辑:遵循这种方法的另一个重要原因是因为它更安全,正如X.Y. 在评论部分指出的那样
在我的示例中,我使用:
http: ^0.13.3
dio: ^4.0.0
flutter_secure_storage: ^4.2.0
jwt_decode: ^0.3.1
flutter_easyloading: ^3.0.0
这个想法是首先检查令牌的到期情况(访问和刷新)。
如果刷新令牌已过期,则清除存储并重定向到 LoginPage。
如果访问令牌已过期,则(在提交实际请求之前)使用刷新令牌刷新它,然后使用刷新后的凭据提交原始请求。通过这种方式,您可以最大限度地减少网络流量并加快响应速度。
我这样做了:
AuthService appAuth = new AuthService();
class AuthService {
Future<void> logout() async {
token = '';
refresh = '';
await Future.delayed(Duration(milliseconds: 100));
Navigator.of(cnt).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginPage()),
(_) => false,
);
}
Future<bool> login(String username, String password) async {
var headers = {'Accept': 'application/json'};
var request = http.MultipartRequest('POST', Uri.parse(baseURL + 'token/'));
request.fields.addAll({'username': username, 'password': password});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
var resp = await response.stream.bytesToString();
final data = jsonDecode(resp);
token = data['access'];
refresh = data['refresh'];
secStore.secureWrite('token', token);
secStore.secureWrite('refresh', refresh);
return true;
} else {
return (false);
}
}
Future<bool> refreshToken() async {
var headers = {'Accept': 'application/json'};
var request =
http.MultipartRequest('POST', Uri.parse(baseURL + 'token/refresh/'));
request.fields.addAll({'refresh': refresh});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
final data = jsonDecode(await response.stream.bytesToString());
token = data['access'];
refresh = data['refresh'];
secStore.secureWrite('token', token);
secStore.secureWrite('refresh', refresh);
return true;
} else {
print(response.reasonPhrase);
return false;
}
}
}
然后创建拦截器
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import '../settings/globals.dart';
class AuthInterceptor extends Interceptor {
static bool isRetryCall = false;
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
bool _token = isTokenExpired(token);
bool _refresh = isTokenExpired(refresh);
bool _refreshed = true;
if (_refresh) {
appAuth.logout();
EasyLoading.showInfo(
'Expired session');
DioError _err;
handler.reject(_err);
} else if (_token) {
_refreshed = await appAuth.refreshToken();
}
if (_refreshed) {
options.headers["Authorization"] = "Bearer " + token;
options.headers["Accept"] = "application/json";
handler.next(options);
}
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
handler.next(response);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
handler.next(err);
}
}
安全存储功能来自:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
SecureStorage secStore = new SecureStorage();
class SecureStorage {
final _storage = FlutterSecureStorage();
void addNewItem(String key, String value) async {
await _storage.write(
key: key,
value: value,
iOptions: _getIOSOptions(),
);
}
IOSOptions _getIOSOptions() => IOSOptions(
accountName: _getAccountName(),
);
String _getAccountName() => 'blah_blah_blah';
Future<String> secureRead(String key) async {
String value = await _storage.read(key: key);
return value;
}
Future<void> secureDelete(String key) async {
await _storage.delete(key: key);
}
Future<void> secureWrite(String key, String value) async {
await _storage.write(key: key, value: value);
}
}
检查到期日期:
bool isTokenExpired(String _token) {
DateTime expiryDate = Jwt.getExpiryDate(_token);
bool isExpired = expiryDate.compareTo(DateTime.now()) < 0;
return isExpired;
}
然后是原始请求
var dio = Dio();
Future<Null> getTasks() async {
EasyLoading.show(status: 'Wait ...');
Response response = await dio
.get(baseURL + 'tasks/?task={"foo":"1","bar":"30"}');
if (response.statusCode == 200) {
print('success');
} else {
print(response?.statusCode);
}}
如您所见,Login 和 refreshToken 请求使用 http 包(它们不需要拦截器)。 getTasks 使用 dio 和它的拦截器,以便在一个且唯一的请求中获得其响应