使用Flutter進(jìn)行公司OAuth認(rèn)證

目錄:

[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里操作退敦。

文檔:

  1. 將String轉(zhuǎn)化為Cookie: https://stackoverflow.com/questions/52241089/how-do-i-make-an-http-request-using-cookies-on-flutter
  2. CookieJar: https://github.com/flutterchina/cookie_jar
  3. Dio: https://github.com/flutterchina/dio#cookie-manager * Dio里面的issue回答粘咖,也很有用!侈百!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓮下,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钝域,更是在濱河造成了極大的恐慌讽坏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件例证,死亡現(xiàn)場離奇詭異路呜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)织咧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門胀葱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笙蒙,你說我怎么就攤上這事抵屿。” “怎么了捅位?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵轧葛,是天一觀的道長。 經(jīng)常有香客問我艇搀,道長尿扯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任中符,我火速辦了婚禮姜胖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淀散。我一直安慰自己,他們只是感情好蚜锨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布档插。 她就那樣靜靜地躺著,像睡著了一般亚再。 火紅的嫁衣襯著肌膚如雪郭膛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天氛悬,我揣著相機(jī)與錄音则剃,去河邊找鬼耘柱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棍现,可吹牛的內(nèi)容都是我干的调煎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼己肮,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼士袄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谎僻,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤娄柳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后艘绍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赤拒,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年诱鞠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了需了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡般甲,死狀恐怖肋乍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敷存,我是刑警寧澤墓造,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站锚烦,受9級(jí)特大地震影響觅闽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涮俄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一蛉拙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彻亲,春花似錦孕锄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宙址,卻和暖如春轴脐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工大咱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恬涧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓碴巾,卻偏偏與公主長得像溯捆,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子餐抢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345