通过AppAuth iOS理解Google OIDC服务

来源:互联网 发布:php都开发过什么软件 编辑:程序博客网 时间:2024/06/03 21:58

转载于:http://www.jianshu.com/p/00ae475fb041

通过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。

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


Paste_Image.png

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

// builds authentication requestOIDAuthorizationRequest *request =  [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration                                                clientId:clientID                                            clientSecret:clientSecret                                                  scopes:@[ OIDScopeOpenID, OIDScopeProfile ]                                             redirectURL:redirectURI                                            responseType:OIDResponseTypeCode                                    additionalParameters:nil];// performs authentication requestAppDelegate *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.com11: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 code11:09:51: Authorization response with code: 4/SS-PVx_JADts0XLrN4VDQiK5QUOTd0qvKtYO_UZJO2E//第二步,获取access token和refresh token,参数都通过post发出去。//Google Auth比较有特色的一点是没有client_secret11: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];}}];