目錄:
[TOC]
公司使用的OAuth認(rèn)證分為三步
String firstPath = 'http://www.senergychina:15280/oauth-server/oauth!login.action';
String secondPath = 'http://www.senergychina:15280/oauth-server/oauth!authorize2.action';
String thirdPath = 'http://www.senergychina:15280/stjnhr-web/oauth2-login';
String fourthPath = 'http://www.senergychina.com.cn:15280/stjnhr-web/emp!queryAndroidEmpInfo.action?empno=Z00009';
要請(qǐng)求人事的接口顺少,就要先通過OAuth認(rèn)證别伏。 認(rèn)證流程如下:
st=>start: 開始
e=>end: 結(jié)束
op1=>operation: 第一步: 請(qǐng)求firstPath,拿到 Response.headers["set-cookie"] op2=>operation: 第二步: 使用Cookie,請(qǐng)求 secondPath,返回code op3=>operation: 第三步: 使用code坛猪,請(qǐng)求thirdPath蛙紫。 thirdPath會(huì)重定向到另一個(gè)地址。 這里我們要拿到thirdPath的 headers["set-cookie"]
op4=>operation: 第四步: 將第三步獲取的cookie荷辕,放到headers["cookies"]中. 請(qǐng)求需要的接口凿跳。
st->op1->op2->op3->op4->e
操作代碼如下:
第一步
第一步,拿cookie:
Map<String, String> firstParams = { 'username': 'W00001', 'password': '111111' };
Response res;
try { res = await dio.get(firstPath, queryParameters: firstParams);
firstCookies = res.headers[HttpHeaders.setCookieHeader];
List<Cookie> cookies = resultCookies.toList();
// 請(qǐng)求第二步
oauthSecond();
}
on DioError catch (error) { } }
[圖片上傳失敗...(image-656ac3-1553874001107)] > #
關(guān)于Cookie
cookie值存放在response.header中疮方,可以使用res.headers["set-cookies"]拿到返回值中的set-cookie控嗜,也可以導(dǎo)入 import 'dart:io'; 來使用res.headers[HttpHeaders.setCookieHeader]獲取。
獲取到的cookie返回值是List<String>骡显,如果要存放到本地疆栏,需要使用CookieJar(); 具體使用方法<https://github.com/flutterchina/cookie_jar
CookieJar存儲(chǔ)的時(shí)候,要傳入List<Cookie>,而從header中獲取的類型是List<String>蟆盐。所以中間要進(jìn)行轉(zhuǎn)換承边。代碼如下
CookieJar 的使用
import 'package:cookie_jar/cookie_jar.dart';
void main() async {
List<Cookie> cookies = [new Cookie("name", "wendux"),new Cookie("location", "china")];
var cj = new CookieJar();
//Save cookies cj.saveFromResponse(Uri.parse("https://www.baidu.com/"), cookies);
//Get cookies
List<Cookie> results = cj.loadForRequest(Uri.parse("https://www.baidu.com/xx"));
print(results);
}
# List<String> 轉(zhuǎn)換為 List<Cookie>
_updateCookie(Response response) {
resultCookies = {};
List<String> allCookie = response.headers[HttpHeaders.setCookieHeader];
if (allCookie != null && allCookie.isNotEmpty) {
for (String setCookie in allCookie) {
print('每一個(gè)cookie:' + setCookie.toString());
var cookies = setCookie.split(";");
for (String rowCookie in cookies) {
_setCookie(rowCookie);
}
}
}
}
_setCookie(String rowCookie) {
if (rowCookie.length > 0) {
var keyValue = rowCookie.split("=");
if (keyValue.length == 2) {
var key = keyValue[0].trim();
var value = keyValue[1];
if (key == 'Path' || key == 'Expires') {
return;
}
cookie = Cookie(key, value);
resultCookies.add(cookie);
}
}
}
第二步
使用第一步獲取的Cookie請(qǐng)求code
oauthSecond() async {
Response res;
Map<String, String> params = {
'clientId': "c1ebe466-1cdc-4bd3-ab69-77c3561b9dee",
'responseType': "code"
};
Options options = Options();
options.headers[HttpHeaders.cookieHeader] = firstCookies;
try {
res = await dio.get(secondPath, queryParameters: params, options: options);
code = res.toString();
print('第二步成功:' + res.toString());
oauthThird();
}
catch (error) {
print('第二步失敗:' + error.toString());
};
}
獲取response的Cookie石挂,我們使用HttpHeaders.setCookieHeader博助,而將cookie添加到header的時(shí)候,我們使用HttpHeaders.cookieHeader痹愚。
也就是下圖的位置富岳,當(dāng)然使用setCookieHeader也可以蛔糯。
[圖片上傳失敗...(image-323c6d-1553874001107)]
Dio的Header放在options中。如果接口需要傳入固定的cookie窖式,那就可以在初始化的時(shí)候蚁飒,直接寫入÷艽或者像步驟二淮逻,在請(qǐng)求的時(shí)候帶入。
第三步 根據(jù)第二步返回的code阁簸,請(qǐng)求要一直使用的cookie
oauthThird() async {
Response res;
Map<String, String> params = { 'code': code, };
try{
BaseOptions op = dio.options;
op.followRedirects = false;
res = await dio.get(thirdPath, queryParameters: params);
print('第三步headers的值是:' + res.headers.toString());
print('第三步realUri: ' + res.realUri.toString());
var redirects = res.redirects;
for (var redirect in redirects) {
print('第三步重定向信息:{method:${redirect.method}, Code: ${redirect.statusCode}, ${redirect.location}');
}
_updateCookie(res);
print("第三步成功之后的cookies的值是:${resultCookies.toString()}");
print('是否重定向:' + res.isRedirect.toString());
/// 如果有Cookie就可以正常請(qǐng)求數(shù)據(jù) oauthFourth();
}
on DioError catch (error) {
if (error.response.statusCode == 302) {
persistentCookies = error.response.headers[HttpHeaders.setCookieHeader];
oauthFourth();
}
}
這一步爬早,默認(rèn)的會(huì)重定向到網(wǎng)頁。即302.
難以解決的問題就是:我們需要的是第三步接口返回的Cookie启妹,但是重定向完成之后筛严,response 是重定向后的Response,而不是第三步接口的response饶米。
獲取重定向后302桨啃,前一個(gè)頁面的Cookie。
解決:在options中檬输,有一個(gè)參數(shù)followRedirects,這個(gè)參數(shù)默認(rèn)是true照瘾,也就是允許重定向,還有一個(gè)maxRedirects參數(shù)褪猛,是允許重定向的次數(shù)网杆。我們可以將followRedirects設(shè)置為false羹饰,不允許重定向伊滋,那么接口就會(huì)在請(qǐng)求完成之后停止。所以可以在catch中獲取到DioErrorType.Response,在catch中判斷队秩,如果是重定向笑旺,就從error中拿cookie。至此馍资,我們就拿到了cookie筒主。
優(yōu)化
優(yōu)化1:
最后一步的時(shí)候,302會(huì)進(jìn)入catch中鸟蟹,作為錯(cuò)誤返回給我們乌妙,但是這并不是我們想要的。 Dio的options中建钥,有一個(gè)validateStatus藤韵,這個(gè)callBack允許用戶直接設(shè)置返回值中的StatusCode是多少的時(shí)候進(jìn)success,其他的進(jìn)failure熊经。使用如下
baseOptions.validateStatus = (int state) {
return (state == 302 || state == 200);
};
所以我們可以將302作為成功返回泽艘,這樣我們就不必在catch中獲取欲险,而是像第一步一樣,直接使用res.headers[HttpHeaders.setCookieHeader]即可匹涮。
優(yōu)化2
由于請(qǐng)求是異步的天试,所以每次請(qǐng)求可以使用Future來優(yōu)化寫法。
try {
res = await dio.get(firstPath, queryParameters: firstParams);
firstCookies = res.headers[HttpHeaders.setCookieHeader];
List<Cookie> cookies = resultCookies.toList();
// 請(qǐng)求第二步 oauthSecond();
}
on DioError catch (error) { }
可優(yōu)化為:
tokenDio.get(firstPath,queryParameters: firstParams).then((res){
print('第一步成功:' + res.headers[HttpHeaders.setCookieHeader].toString());
baseOptions.headers[HttpHeaders.cookieHeader] = res.headers[HttpHeaders.setCookieHeader];
return tokenDio.get(secondPath, queryParameters: secondParams);
})
.catchError((Object error){
print('捕捉到錯(cuò)誤' + error.toString());
}).whenComplete(() {})
同時(shí)然低,這種寫法可以串聯(lián)多個(gè)喜每,正好符合認(rèn)證的流程,代碼如下
tokenDio.options = baseOptions; baseOptions.validateStatus = (int state) {
return (state == 302 || state == 200);
};
tokenDio.get(firstPath,queryParameters: firstParams).then((res){
print('第一步成功:' + res.headers[HttpHeaders.setCookieHeader].toString());
baseOptions.headers[HttpHeaders.cookieHeader] = res.headers[HttpHeaders.setCookieHeader];
return tokenDio.get(secondPath, queryParameters: secondParams);
}).then((res){
print('第二步code的值是:' + res.toString());
baseOptions.followRedirects = false;
Map<String, String> thirdParams = { 'code': res.toString(), };
return tokenDio.get(thirdPath, queryParameters: thirdParams);
}).then((Response res){
// 將BaseOptions重置 baseOptions = BaseOptions();
print('這時(shí)候的res是:' + res.headers[HttpHeaders.setCookieHeader].toString());
tokenDio.options.headers[HttpHeaders.cookieHeader] = persistentCookies = options.headers[HttpHeaders.cookieHeader] = persistentCookies = res.headers[HttpHeaders.setCookieHeader];
fetchFourth();
})
.catchError((Object error){
print('捕捉到錯(cuò)誤' + error.toString());
}).whenComplete(() {});
串聯(lián)的時(shí)候雳攘,每次返回新的請(qǐng)求灼卢,就可以繼續(xù)串聯(lián)then,進(jìn)行操作来农。而且經(jīng)過優(yōu)化1的操作鞋真,我們可以在第三步直接在then里操作,如果沒有優(yōu)化1的操作沃于,我們第三步重定向的結(jié)果涩咖,就需要在catch中處理,也就無法繼續(xù)串聯(lián)下去繁莹。
優(yōu)化3
Dio2.0版本以后檩互,可以使用intercepters,我們可以在intercepter中攔截處理:
dio.interceptors
..add(CookieManager(CookieJar()))
..add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
print("請(qǐng)求前:" + options.toString());
return options;
},
onResponse: (Response response){
print("請(qǐng)求結(jié)果:" + response.headers[HttpHeaders.setCookieHeader].toString());
},
onError: (error){
print("請(qǐng)求錯(cuò)誤:" + error.toString());
}),
);
那么咨演,我們就可以進(jìn)一步進(jìn)行優(yōu)化闸昨。 如果第一次本地沒有請(qǐng)求的緩存,我們就可以在請(qǐng)求前進(jìn)行判斷薄风,然后進(jìn)行認(rèn)證: 代碼如下
Dio _dio;
BaseOptions baseOptions = BaseOptions();
List<String> persistentCookies = [];
Dio tokenDio = Dio();
Dio get dio {
if (_dio != null) {
return _dio;
}
// persistentCookies = [];
_dio = Dio();
if (persistentCookies.isNotEmpty) {
baseOptions.headers[HttpHeaders.cookieHeader] = persistentCookies;
}
_dio.interceptors
..add(InterceptorsWrapper(
onRequest: (RequestOptions options){
if (persistentCookies.isEmpty) {
(tokenDio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.findProxy = (uri) {
return "PROXY 192.168.40.83:8888";
};
};
tokenDio.options = baseOptions;
baseOptions.validateStatus = (int state) {
return (state == 302 || state == 200);
};
dio.lock();
return tokenDio.get(firstPath,queryParameters: firstParams).then((res){
print('第一步成功:' + res.headers[HttpHeaders.setCookieHeader].toString());
baseOptions.headers[HttpHeaders.cookieHeader] = res.headers[HttpHeaders.setCookieHeader];
return tokenDio.get(secondPath, queryParameters: secondParams); }).then((res){
print('第二步code的值是:' + res.toString());
baseOptions.followRedirects = false;
Map<String, String> thirdParams = { 'code': res.toString(), };
return tokenDio.get(thirdPath, queryParameters: thirdParams);
}).then((Response res){
// 將BaseOptions重置 baseOptions = BaseOptions();
print('這時(shí)候的res是:' + res.headers[HttpHeaders.setCookieHeader].toString());
tokenDio.options.headers[HttpHeaders.cookieHeader] = persistentCookies = options.headers[HttpHeaders.cookieHeader] = persistentCookies = res.headers[HttpHeaders.setCookieHeader];
return options;
}).catchError((Object error){
print('捕捉到錯(cuò)誤' + error.toString());
}).whenComplete(() => dio.unlock());
}
else {
options.headers[HttpHeaders.cookieHeader] = persistentCookies;
return options;
} },
onResponse: (Response response){ },
onError: (Object error){ }));
return _dio;
}
getData() async {
Response res;
try {
res = await dio.get(fourthPath);
print('第四步的結(jié)果:' + res.toString());
}
catch(error){
print('第四步錯(cuò)誤:' + error.toString());
};
}
使用的時(shí)候饵较,直接調(diào)用 getData()請(qǐng)求數(shù)據(jù)即可。 第一次的時(shí)候遭赂,由于persistentCookie本地沒有數(shù)據(jù)循诉,所以會(huì)自動(dòng)認(rèn)證。
注意:
- 這里使用了兩個(gè)dio對(duì)象請(qǐng)求撇他,tokenDio專門用來請(qǐng)求token茄猫。
- onRequest: 的callBack里面,必須返回options困肩,即onRequest: (RequestOptions options)划纽,改造這里面的options。如果返回baseOptions锌畸,請(qǐng)求會(huì)無法繼續(xù)
- dio.lock();可以暫時(shí)上鎖勇劣,在認(rèn)證完成后解鎖,即可繼續(xù)進(jìn)行請(qǐng)求蹋绽。 這里只是暫時(shí)對(duì)請(qǐng)求前沒有cookie進(jìn)行了封裝芭毙。如果是cookie過期筋蓖,可以根據(jù)返回的值再次進(jìn)行認(rèn)證,即在onResponse: (Response response){}這個(gè)callBack里操作退敦。
文檔:
- 將String轉(zhuǎn)化為Cookie: https://stackoverflow.com/questions/52241089/how-do-i-make-an-http-request-using-cookies-on-flutter
- CookieJar: https://github.com/flutterchina/cookie_jar
- Dio: https://github.com/flutterchina/dio#cookie-manager * Dio里面的issue回答粘咖,也很有用!侈百!