目录:
[TOC]
公司使用的OAuth认证分为三步
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';要请求人事的接口,就要先通过OAuth认证。 认证流程如下:
st=>start: 开始
e=>end: 结束
op1=>operation: 第一步: 请求firstPath,拿到 Response.headers["set-cookie"] op2=>operation: 第二步: 使用Cookie,请求 secondPath,返回code op3=>operation: 第三步: 使用code,请求thirdPath。 thirdPath会重定向到另一个地址。 这里我们要拿到thirdPath的 headers["set-cookie"]
op4=>operation: 第四步: 将第三步获取的cookie,放到headers["cookies"]中. 请求需要的接口。
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();
// 请求第二步
oauthSecond();
on DioError catch (error) { } }
[图片上传失败...(image-656ac3-1553874001107)] > #
关于Cookie
cookie值存放在response.header中,可以使用res.headers["set-cookies"]拿到返回值中的set-cookie,也可以导入 import 'dart:io'; 来使用res.headers[HttpHeaders.setCookieHeader]获取。
获取到的cookie返回值是List<String>,如果要存放到本地,需要使用CookieJar(); 具体使用方法<https://github.com/flutterchina/cookie_jar
CookieJar存储的时候,要传入List<Cookie>,而从header中获取的类型是List<String>。所以中间要进行转换。代码如下
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> 转换为 List<Cookie>
_updateCookie(Response response) {
resultCookies = {};
List<String> allCookie = response.headers[HttpHeaders.setCookieHeader];
if (allCookie != null && allCookie.isNotEmpty) {
for (String setCookie in allCookie) {
print('每一个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请求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的时候,我们使用HttpHeaders.cookieHeader。
也就是下图的位置,当然使用setCookieHeader也可以。
[图片上传失败...(image-323c6d-1553874001107)]
Dio的Header放在options中。如果接口需要传入固定的cookie,那就可以在初始化的时候,直接写入。或者像步骤二,在请求的时候带入。
第三步 根据第二步返回的code,请求要一直使用的cookie
oauthThird() async {
Response res;
Map<String, String> params = { 'code': code, };
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就可以正常请求数据 oauthFourth();
on DioError catch (error) {
if (error.response.statusCode == 302) {
persistentCookies = error.response.headers[HttpHeaders.setCookieHeader];
oauthFourth();
这一步,默认的会重定向到网页。即302.
难以解决的问题就是:我们需要的是第三步接口返回的Cookie,但是重定向完成之后,response 是重定向后的Response,而不是第三步接口的response。
获取重定向后302,前一个页面的Cookie。
解决:在options中,有一个参数followRedirects,这个参数默认是true,也就是允许重定向,还有一个maxRedirects参数,是允许重定向的次数。我们可以将followRedirects设置为false,不允许重定向,那么接口就会在请求完成之后停止。所以可以在catch中获取到DioErrorType.Response,在catch中判断,如果是重定向,就从error中拿cookie。至此,我们就拿到了cookie。
最后一步的时候,302会进入catch中,作为错误返回给我们,但是这并不是我们想要的。 Dio的options中,有一个validateStatus,这个callBack允许用户直接设置返回值中的StatusCode是多少的时候进success,其他的进failure。使用如下
baseOptions.validateStatus = (int state) {
return (state == 302 || state == 200);
所以我们可以将302作为成功返回,这样我们就不必在catch中获取,而是像第一步一样,直接使用res.headers[HttpHeaders.setCookieHeader]即可。
由于请求是异步的,所以每次请求可以使用Future来优化写法。
try {
res = await dio.get(firstPath, queryParameters: firstParams);
firstCookies = res.headers[HttpHeaders.setCookieHeader];
List<Cookie> cookies = resultCookies.toList();
// 请求第二步 oauthSecond();
on DioError catch (error) { }
可优化为:
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('捕捉到错误' + error.toString());
}).whenComplete(() {})
同时,这种写法可以串联多个,正好符合认证的流程,代码如下
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('这时候的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('捕捉到错误' + error.toString());
}).whenComplete(() {});
串联的时候,每次返回新的请求,就可以继续串联then,进行操作。而且经过优化1的操作,我们可以在第三步直接在then里操作,如果没有优化1的操作,我们第三步重定向的结果,就需要在catch中处理,也就无法继续串联下去。
Dio2.0版本以后,可以使用intercepters,我们可以在intercepter中拦截处理:
dio.interceptors
..add(CookieManager(CookieJar()))
..add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
print("请求前:" + options.toString());
return options;
onResponse: (Response response){
print("请求结果:" + response.headers[HttpHeaders.setCookieHeader].toString());
onError: (error){
print("请求错误:" + error.toString());
那么,我们就可以进一步进行优化。 如果第一次本地没有请求的缓存,我们就可以在请求前进行判断,然后进行认证: 代码如下
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('这时候的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('捕捉到错误' + 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('第四步的结果:' + res.toString());
catch(error){
print('第四步错误:' + error.toString());
使用的时候,直接调用 getData()请求数据即可。 第一次的时候,由于persistentCookie本地没有数据,所以会自动认证。
这里使用了两个dio对象请求,tokenDio专门用来请求token。
onRequest: 的callBack里面,必须返回options,即onRequest: (RequestOptions options),改造这里面的options。如果返回baseOptions,请求会无法继续
dio.lock();可以暂时上锁,在认证完成后解锁,即可继续进行请求。 这里只是暂时对请求前没有cookie进行了封装。如果是cookie过期,可以根据返回的值再次进行认证,即在onResponse: (Response response){}这个callBack里操作。
将String转化为Cookie: https://stackoverflow.com/questions/52241089/how-do-i-make-an-http-request-using-cookies-on-flutter