问题
I am trying to use Interceptor with Dio in flutter, I have to handle Token expire. following is my code
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;
}
problem is instead of repeating the network call with the new token, Dio is returning the error object to the calling method, which in turn is rendering the wrong widget, any leads on how to handle token refresh with dio?
回答1:
I solved it using interceptors in following way :-
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;
}
回答2:
You would get a response status code as 401 for token expiration. In order to request new access token, you need to use post method along with form data and required Dio's options (content-type and headers). Below is the code shows how to request new token.
After successful request, if you get the response status code as 200, then you will get new access token value along with refresh token value and save them in any storage you prefer to use. For example, Shared preferences.
Once you have new access token saved, you can use it to fetch data using get method shown in the same code below.
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;
}
回答3:
I not finished the Custom Http Client implementation yet but it can help you.
Custom Http
Interceptor
Main
回答4:
I have found a simple solution that looks like the following:
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;
}));
Basically what is going on is it checks to see if the error is a 401
or 403
, which are common auth errors, and if so, it will refresh the token and retry the response. My implementation of refreshToken()
looks like the following, but this may vary based on your 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'];
}
}
I use Flutter Sercure Storage to store the accessToken. My retry method looks like the following:
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);
}
If you want to easily allows add the access_token
to the request I suggest adding the following function when you declare your dio router with the onError
callback:
onRequest: (options) async {
options.headers['Authorization'] = 'Bearer: $accessToken';
return options;
},
来源:https://stackoverflow.com/questions/56740793/using-interceptor-in-dio-for-flutter-to-refresh-token