我正试图在Dio中使用Interceptor和Dio,我必须处理令牌过期。 以下是我的代码
Future<Dio> getApiClient() async {
token = await storage.read(key: USER_TOKEN);
_dio.interceptors.clear();
_dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) {
// Do something before request is sent
options.headers["Authorization"] = "Bearer " + token;
return options;
},onResponse:(Response response) {
// Do something with response data
return response; // continue
}, onError: (DioError error) async {
// Do something with response error
if (error.response?.statusCode == 403) {
// update token and repeat
// Lock to block the incoming request until the token updated
_dio.interceptors.requestLock.lock();
_dio.interceptors.responseLock.lock();
RequestOptions options = error.response.request;
FirebaseUser user = await FirebaseAuth.instance.currentUser();
token = await user.getIdToken(refresh: true);
await writeAuthKey(token);
options.headers["Authorization"] = "Bearer " + token;
_dio.interceptors.requestLock.unlock();
_dio.interceptors.responseLock.unlock();
_dio.request(options.path, options: options);
} else {
return error;
}
}));
_dio.options.baseUrl = baseUrl;
return _dio;
}
问题不是用新令牌重复网络调用,而是Dio将错误对象返回到调用方法,该方法又呈现了错误的小部件,有关如何使用dio处理令牌刷新的任何线索?
答案 0 :(得分:4)
对于令牌到期,您将获得响应状态码401。为了请求新的访问令牌,您需要使用post方法以及表单数据和必需的Dio的选项(内容类型和标头)。下面的代码显示了如何请求新令牌。
成功请求后,如果获得的响应状态代码为200,则将获得新的访问令牌值以及刷新令牌值,并将它们保存在您希望使用的任何存储中。例如,共享首选项。
一旦保存了新的访问令牌,就可以使用下面的相同代码中所示的get方法使用它来获取数据。
onError(DioError error) async {
if (error.response?.statusCode == 401) {
Response response;
var authToken = base64
.encode(utf8.encode("username_value" + ":" + "password_value"));
FormData formData = new FormData.from(
{"grant_type": "refresh_token", "refresh_token": refresh_token_value});
response = await dio.post(
url,
data: formData,
options: new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded"),
headers: {HttpHeaders.authorizationHeader: 'Basic $authToken'}),
);
if (response.statusCode == 200) {
response = await dio.get(
url,
options: new Options(headers: {
HttpHeaders.authorizationHeader: 'Bearer access_token_value'
}),
);
return response;
} else {
print(response.data);
return null;
}
}
return error;
}
答案 1 :(得分:4)
我找到了一个简单的解决方案,如下所示:
this.api = Dio();
this.api.interceptors.add(InterceptorsWrapper(
onError: (error) async {
if (error.response?.statusCode == 403 ||
error.response?.statusCode == 401) {
await refreshToken();
return _retry(error.request);
}
return error.response;
}));
基本上,这是检查错误是常见的auth错误是401
还是403
,如果是,它将刷新令牌并重试响应。我对refreshToken()
的实现如下所示,但这取决于您的api:
Future<void> refreshToken() async {
final refreshToken = await this._storage.read(key: 'refreshToken');
final response =
await this.api.post('/users/refresh', data: {'token': refreshToken});
if (response.statusCode == 200) {
this.accessToken = response.data['accessToken'];
}
}
我使用Flutter Sercure Storage来存储accessToken。我的重试方法如下:
Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = new Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return this.api.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
如果您想轻松地将access_token
添加到请求中,建议您在使用onError
回调声明dio路由器时添加以下功能:
onRequest: (options) async {
options.headers['Authorization'] = 'Bearer: $accessToken';
return options;
},
答案 2 :(得分:2)
答案 3 :(得分:2)
我通过以下方式使用拦截器解决了它:-
Future<Dio> getApiClient() async {
token = await storage.read(key: USER_TOKEN);
_dio.interceptors.clear();
_dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) {
// Do something before request is sent
options.headers["Authorization"] = "Bearer " + token;
return options;
},onResponse:(Response response) {
// Do something with response data
return response; // continue
}, onError: (DioError error) async {
// Do something with response error
if (error.response?.statusCode == 403) {
_dio.interceptors.requestLock.lock();
_dio.interceptors.responseLock.lock();
RequestOptions options = error.response.request;
FirebaseUser user = await FirebaseAuth.instance.currentUser();
token = await user.getIdToken(refresh: true);
await writeAuthKey(token);
options.headers["Authorization"] = "Bearer " + token;
_dio.interceptors.requestLock.unlock();
_dio.interceptors.responseLock.unlock();
return _dio.request(options.path,options: options);
} else {
return error;
}
}));
_dio.options.baseUrl = baseUrl;
return _dio;
}
答案 4 :(得分:2)
Dio 4.0.0
from global_land_mask import globe
def crosses_land(x1,y1,x2,y2):
# your geo points
#x1, y1 = 13.26077,100.81099
#x2, y2 = 13.13237,100.82993
# the increment step (higher = faster)
STEP = 0.0003
if x1 > x2: # x2 must be the bigger one here
x1, x2 = x2, x1
y1, y2 = y2, y1
for i in range(int((x2-x1)/STEP) + 1):
try:
x = x1 + i*STEP
y = (y1-y2)/(x1-x2) * (x - x1) + y1
except:
continue
is_on_land = globe.is_land(float(x), float(y))
#if not is_on_land:
#print("in water")
if is_on_land:
#print("crosses land")
return True
print(crosses_land(x1,y1,x2,y2))
答案 5 :(得分:0)
下面是我的拦截器的一个片段
dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
/* Write your request logic setting your Authorization header from prefs*/
String token = await prefs.accessToken;
if (token != null) {
options.headers["Authorization"] = "Bearer " + token;
return options; //continue
}, onResponse: (Response response) async {
// Write your response logic
return response; // continue
}, onError: (DioError dioError) async {
// Refresh Token
if (dioError.response?.statusCode == 401) {
Response response;
var data = <String, dynamic>{
"grant_type": "refresh_token",
"refresh_token": await prefs.refreshToken,
'email': await prefs.userEmail
};
response = await dio
.post("api/url/for/refresh/token", data: data);
if (response.statusCode == 200) {
var newRefreshToken = response.data["data"]["refresh_token"]; // get new refresh token from response
var newAccessToken = response.data["data"]["access_token"]; // get new access token from response
prefs.refreshToken = newRefreshToken;
prefs.accessToken = newAccessToken; // to be used in the request section of the interceptor
return dio.request(dioError.request.baseUrl + dioError.request.path,
options: dioError.request);
}
}
return dioError;
}));
return dio;
}
}
答案 6 :(得分:0)
它正在 100% 工作
RestClient client;
static BaseOptions options = new BaseOptions(
connectTimeout: 5000,
receiveTimeout: 3000,
);
RemoteService() {
// or new Dio with a BaseOptions instance.
final dio = Dio(options);
dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
// Do something before request is sent
return options; //continue
}, onResponse: (Response response) async {
// Do something with response data
return response; // continue
}, onError: (DioError error) async {
// Do something with response error
if (error.response.statusCode == 401) {
Response response =
await dio.post("http://addrees-server/oauth/token",
options: Options(
headers: {
'Authorization': ApiUtils.BASIC_TOKEN,
'Content-Type': ApiUtils.CONTENT_TYPE,
},
),
queryParameters: {
"grant_type": ApiUtils.GRANT_TYPE,
"username": AppConstants.LOGIN,
"password": AppConstants.PASSWORD
});
Sessions.access_token = response.data['access_token'];
error.response.request.queryParameters
.update('access_token', (value) => Sessions.access_token);
RequestOptions options = error.response.request;
return dio.request(options.path, options: options); //continue
} else {
return error;
}
}));
client = RestClient(dio);
}
答案 7 :(得分:0)
我认为更好的方法是在您实际发出请求之前检查令牌。这样一来,您的网络流量就会减少,响应速度也会更快。
在我的示例中,我使用:
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 和它的拦截器,以便在一个且唯一的请求中获得响应