通過AppAuth-iOS體驗(yàn)Google OIDC服務(wù)非常容易,只需要配置下面三個(gè)配置項(xiàng)愧沟。
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所有的相關(guān)信息,非常豐富的信息。這個(gè)配置文件的詳細(xì)介紹可以參考:OpenID Connect Discovery 1.0 incorporating errata set 1械馆。阿里云和Azure的OIDC Discovery鏈接如下所示。
- 阿里云:https://oauth.aliyun.com/.well-known/openid-configuration
- 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把a(bǔ)uth和login放在accounts.google.com
域下面冶忱,token單獨(dú)放在www.googleapis.com
域下面尾菇。
接下來看看如何發(fā)起授權(quán)。構(gòu)造OIDAuthorizationRequest請(qǐng)求之后囚枪,使用In-App browser(iOS使用SFSafariViewController)打開相應(yīng)的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的流轉(zhuǎn)關(guān)系链沼。結(jié)果沒法使用UIWebView默赂,Google發(fā)現(xiàn)是UIWebView會(huì)直接報(bào)錯(cuò)。
Google Auth返回的id_token已經(jīng)包含了用戶信息括勺,但是還是提供一個(gè)單獨(dú)的userinfo_endpoint去換取用戶信息缆八。
手動(dòng)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奈辰,參數(shù)都在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奖恰,參數(shù)都通過post發(fā)出去。
//Google Auth比較有特色的一點(diǎn)是沒有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
上面的參數(shù)中宛裕,code_challenge_method
瑟啃、code_challenge
、code_verifier
這三個(gè)看起來很神秘揩尸。它們是為了避免auth code被攔截而做了保護(hù)措施蛹屿。第一步通過code_challenge_method
給code_verifier
做一次計(jì)算得到code_challenge
。Authorization Endpoint會(huì)保存code_challenge
相關(guān)的信息疲酌。第二步直接把code_verifier
傳遞給token endpoint蜡峰,服務(wù)器端做一次對(duì)比了袁。如果對(duì)不上,就報(bào)錯(cuò)湿颅。詳細(xì)信息請(qǐng)參看:Proof Key for Code Exchange by OAuth Public Clients载绿。
繼續(xù)往下分析一下state這個(gè)參數(shù)的作用吧。state參數(shù)是為了防止CSRF攻擊油航,詳細(xì)信息請(qǐng)參看:OAuth2:忽略 state 參數(shù)引發(fā)的 csrf 漏洞 #68崭庸。In-App browser回跳到App時(shí),要檢查一下URL里面的state是否跟之前的state一致谊囚。
OpenID Connect標(biāo)準(zhǔn)集里面包含一個(gè)Dynamic Client Registration
的標(biāo)準(zhǔn)怕享,用于動(dòng)態(tài)注冊(cè)客戶端,不過我目前還沒有看到哪個(gè)OIDC服務(wù)器的Discovery里面有registration_endpoint
镰踏,包括Google OIDC服務(wù)器函筋。
AppAuth客戶端是支持這個(gè)特性的,很多地方都會(huì)判斷是否配置了client_id奠伪。如果沒有配置的話跌帐,那么通過Dynamic Client Registration
注冊(cè)一個(gè)客戶端。
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];
}