通过AppAuth iOS理解Google OIDC服务

通过AppAuth-iOS体验Google OIDC服务非常容易,只需要配置下面三个配置项。

static NSString *const kIssuer = @"https://accounts.google.com";

static NSString *const kClientID = @"24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef.apps.googleusercontent.com";

static NSString *const kRedirectURI = @"com.googleusercontent.apps.24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef:/oauth2redirect/";

通过https://accounts.google.com/.well-known/openid-configuration可以获取到Google Auth所有的相关信息,非常丰富的信息。这个配置文件的详细介绍可以参考:OpenID Connect Discovery 1.0 incorporating errata set 1。阿里云和Azure的OIDC Discovery链接如下所示。

  1. 阿里云:https://oauth.aliyun.com/.well-known/openid-configuration
  2. Azure:https://login.microsoftonline.com/e86128fb-fc4c-4044-8c6c-98002346bc88/v2.0/.well-known/openid-configuration,格式是https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration,tenantId可以去Azure portal里面的AD里面查看到。

从配置信息可以看出Google把auth和login放在accounts.google.com域下面,token单独放在www.googleapis.com域下面。

Paste_Image.png

接下来看看如何发起授权。构造OIDAuthorizationRequest请求之后,使用In-App browser(iOS使用SFSafariViewController)打开相应的URL。

// builds authentication request
OIDAuthorizationRequest *request =
  [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
                                                clientId:clientID
                                            clientSecret:clientSecret
                                                  scopes:@[ OIDScopeOpenID, OIDScopeProfile ]
                                             redirectURL:redirectURI
                                            responseType:OIDResponseTypeCode
                                    additionalParameters:nil];
// performs authentication request
AppDelegate *appDelegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
[self logMessage:@"Initiating authorization request %@", request];
appDelegate.currentAuthorizationFlow =
  [OIDAuthorizationService presentAuthorizationRequest:request
      presentingViewController:self
                      callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
                                 NSError *_Nullable error) {
    if (authorizationResponse) {
      OIDAuthState *authState =
          [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse];
      [self setAuthState:authState];

      [self logMessage:@"Authorization response with code: %@",
                       authorizationResponse.authorizationCode];
      // could just call [self tokenExchange:nil] directly, but will let the user initiate it.
    } else {
      [self logMessage:@"Authorization error: %@", [error localizedDescription]];
    }
  }];

- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL {
  SFSafariViewController *safariViewController =
      [[SFSafariViewController alloc] initWithURL:URL entersReaderIfAvailable:NO];
  return safariViewController;
}

本来我还想将SFSafariViewController改成UIWebView,以观察所有URL的流转关系。结果没法使用UIWebView,Google发现是UIWebView会直接报错。

Paste_Image.png

Google Auth返回的id_token已经包含了用户信息,但是还是提供一个单独的userinfo_endpoint去换取用户信息。

Paste_Image.png

手动authorize流程如下所示。

11:09:02: Fetching configuration for issuer: https://accounts.google.com
11:09:02: Got configuration: OIDServiceConfiguration authorizationEndpoint: https://accounts.google.com/o/oauth2/v2/auth, tokenEndpoint: https://www.googleapis.com/oauth2/v4/token, registrationEndpoint: (null), discoveryDocument: [<OIDServiceDiscovery: 0x608000012970>]

11:09:02: Initiating authorization request <OIDAuthorizationRequest: 0x6000000ac180, request: 

//第一步,获取auth code,参数都在URL里面。
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code
&code_challenge_method=S256
&scope=openid%20profile
&code_challenge=zkJhG3SZa9vHx8P8TvikiEozUDNQwlJXnlskEe0wJGA
&redirect_uri=com.googleusercontent.apps.24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef:/oauth2redirect/
&client_id=24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef.apps.googleusercontent.com
&state=KX_4c-UQCxrshPrtHD75CLPoj80gipzxTr1r2hGNhus>

//拿到auth code
11:09:51: Authorization response with code: 4/SS-PVx_JADts0XLrN4VDQiK5QUOTd0qvKtYO_UZJO2E

//第二步,获取access token和refresh token,参数都通过post发出去。
//Google Auth比较有特色的一点是没有client_secret
11:09:52: Performing authorization code exchange with request [<OIDTokenRequest: 0x6080000ac360, request: <URL: https://www.googleapis.com/oauth2/v4/token, 
HTTPBody: code=4/SS-PVx_JADts0XLrN4VDQiK5QUOTd0qvKtYO_UZJO2E
&code_verifier=9zUuBzJkfD4y8Ei-424Cx-lWIwObhnSbC5_dOGZXSCk
&redirect_uri=com.googleusercontent.apps.24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef:/oauth2redirect/
&client_id=24408797720-p6d3uj34k564kl4s85o2d1rdgvlp7nef.apps.googleusercontent.com
&grant_type=authorization_code>>]

11:09:53: Received token response with accessToken: ya29.GltBBBYWx0rhh87WWK1oV0peHCI9_EwwoWWf4lUiwBc_--bTXv_Ag6OWdBU6SAHhYx5O6RdOsX1HMCPcELfeIlw-5rESYDuGmyRTqAuchGbHl1Ws-hC10O80YAmi

上面的参数中,code_challenge_methodcode_challengecode_verifier这三个看起来很神秘。它们是为了避免auth code被拦截而做了保护措施。第一步通过code_challenge_methodcode_verifier做一次计算得到code_challenge。Authorization Endpoint会保存code_challenge相关的信息。第二步直接把code_verifier传递给token endpoint,服务器端做一次对比。如果对不上,就报错。详细信息请参看:Proof Key for Code Exchange by OAuth Public Clients

Paste_Image.png
Paste_Image.png

继续往下分析一下state这个参数的作用吧。state参数是为了防止CSRF攻击,详细信息请参看:OAuth2:忽略 state 参数引发的 csrf 漏洞 #68。In-App browser回跳到App时,要检查一下URL里面的state是否跟之前的state一致。

Paste_Image.png

OpenID Connect标准集里面包含一个Dynamic Client Registration的标准,用于动态注册客户端,不过我目前还没有看到哪个OIDC服务器的Discovery里面有registration_endpoint,包括Google OIDC服务器。

AppAuth客户端是支持这个特性的,很多地方都会判断是否配置了client_id。如果没有配置的话,那么通过Dynamic Client Registration注册一个客户端。

if (!kClientID) {
  [self doClientRegistration:configuration
                    callback:^(OIDServiceConfiguration *configuration,
                               OIDRegistrationResponse *registrationResponse) {
    [self doAuthWithAutoCodeExchange:configuration
                            clientID:registrationResponse.clientID
                        clientSecret:registrationResponse.clientSecret];
  }];
} else {
  [self doAuthWithAutoCodeExchange:configuration clientID:kClientID clientSecret:nil];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容