oauth2.0によるweb apiの保護
TRANSCRIPT
OAuth 2.0 によるWeb API の保護
2016.12.21
Japan Web API Community #1
Naohiro Fujie
@phr_eidentity
自己紹介
• Blog• IdM実験室:http://idmlab.eidentity.jp
• Modules(codeplex)• Generic REST MA for FIM/MIM:https://restmafim.codeplex.com/
• 記事 / 書籍• 記事 : @IT/企業のID管理/シングルサインオンの新しい選択肢「IDaaS」の活用 他
• 監訳 : クラウド時代の認証基盤 Azure Active Directory 完全解説
• 共著 : クラウド環境におけるアイデンティティ管理ガイドライン
• その他• JNSA アイデンティティ管理WG
• OpenID Foundation Japan 教育・翻訳WG、エンタープライズ・アイデンティティWG
• Microsoft MVP for Enterprise Mobility(Jan 2010 -)
© 2016 Naohiro Fujie 2
Agenda
1. WebAPI に求められるセキュリティ• 利用者から見た課題
• API 提供者から見た課題
2. OAuth 2.0 の概要と課題への対応
3. デモ• Azure AD による WebAPI の保護
• ADAL による API クライアントの実装
© 2016 Naohiro Fujie 3
WebAPI に求められるセキュリティ
© 2016 Naohiro Fujie 4
WebAPI 利用・提供時の課題
• いかにして API クライアントを信頼するか?• 利用者の視点
• API を通して提供される自身のデータを悪用されたくない
• API 提供者の視点• 誰が利用しているのかを明確にしたい(例:課金)
• 人の識別
• API クライアントの識別
• 提供するデータを悪用されたくない
© 2016 Naohiro Fujie 5
http(s)でインターネット上に公開しているので、Firewall 等による分離は難しい
API クライアントは便利だけど、間接的に自身のデータへアクセスされるのは怖い
API の基本は単機能。ユーザ認証・認可機能を API 毎に開
発したくない
適切な API クライアントからだけアクセスさせたい
利用者から見た課題
© 2016 Naohiro Fujie 6
従来の Web アプリケーションの利用
• ユーザ自身によりコンテキストの使い分けが可能• Facebookの友達からはアプリA、Twitter上のアイデンティティとの紐
づけをされても良いが、反対は嫌
• 情報は二重管理
© 2016 Naohiro Fujie 7
実名、電話番号を登録
ニックネームを登録
Twitterには同僚に知られたくない性癖を投稿しているので、
実名は登録したくない
Facebookの友達は近しい人ばかりなので、実名や電話番
号を登録しておきたい
アプリAニックネームと電話番号を登録
アプリAにはニックネームと電話番号を登録したい・・・
二重管理は面倒
上手く API を使って二重管理をやめたい
• アプリにFacebookとTwitterのIDとパスワードを登録する?
© 2016 Naohiro Fujie 8
実名、電話番号を登録
ニックネームを登録
アプリA(APIクライアント)
ニックネームと電話番号を使わせたい
ユーザのID/PWDを使ってFacebookへアクセス
複数のサービスに同じ情報を登録するのは面倒なので便利なマッシュアップ・アプリを
使いたい
ユーザのID/PWDを使ってTwitterへアクセス
パスワード?
• アプリ上に個人のパスワードを登録するのは・・・
© 2016 Naohiro Fujie 9
実名、電話番号を登録
ニックネームを登録
アプリA(APIクライアント)
ニックネームと電話番号を使わせたい
ユーザのID/PWDを使ってFacebookへアクセス
ユーザのID/PWDを使ってTwitterへアクセス
パスワードを登録しておくのはかなり嫌・・・
漏えい?変更時の運用?
コンテキストが混ざる
• せっかく分けていたコンテキストがアプリで混ざる
© 2016 Naohiro Fujie 10
実名、電話番号を登録
ニックネームを登録
アプリA(APIクライアント)
ニックネームと電話番号を使わせたい
電話番号だけが欲しいのに、実名もとれてしまう?
このアプリ上でコンテキストが混ざってしまう
課題:パスワード、コンテキスト
パスワードを使わずに
どうやってユーザの代わりに「望んだ範囲に限定して」アクセスさせるか?
© 2016 Naohiro Fujie 11
実名、電話番号を登録
ニックネームを登録
アプリA(APIクライアント)
ニックネームと電話番号を使わせたい
パスワードを登録しておくのはかなり嫌・・・
漏えい?変更時の運用?
このアプリ上でコンテキストが混ざってしまう
API 提供者から見た課題
© 2016 Naohiro Fujie 12
従来の Web アプリケーションの利用
• ユーザが直接アプリを利用していたので識別は簡単
• 個々に認証機能や UI 開発が必要
© 2016 Naohiro Fujie 13
直接ログイン
直接ログイン
アプリA直接ログイン
個々に運用・管理。識別は簡単だが、個々に機能
開発が必要
API を公開してシンプルにしたい
• ユーザが直接アプリを利用していたので識別は簡単
• 個々に認証機能や UI 開発が必要
© 2016 Naohiro Fujie 14
アプリA(APIクライアント) シンプルな機能・データ提供
だけを API で行いたい
利用
アプリ経由で利用
アプリ経由で利用
アクセス元の特定・真贋が難しい
• アクセス元アプリの信頼性の担保が難しい
© 2016 Naohiro Fujie 15
アプリA(APIクライアント) 誰がどのアプリ経由が使って
いるのかわからない?
利用
アプリ経由で利用
アプリ経由で利用このアプリは信頼できるのか?
特定アプリ以外からのアクセスを防げない?
課題:利用元の特定と管理
• どうやって「許可された」アプリからのみアクセスを許可するか?
© 2016 Naohiro Fujie 16
アプリA(APIクライアント)
利用
アプリ経由で利用
アプリ経由で利用このアプリは信頼できるのか?
特定アプリ以外からのアクセスを防げない?
誰がどのアプリ経由が使っているのかわからない?
OAuth 2.0 の概要と課題への対応
© 2016 Naohiro Fujie 17
OAuth の概要と基本的な考え方
• 目的• リソースへのアクセスを安全に認可(委譲)すること
• 基本的な考え方• リソース(API を通して提供される機能やデータ)の持ち主はユーザ
である
• ユーザの同意に基づき API クライアントに対してリソースへのアクセス権限を認可(委譲)する
• 認証(検証)された API クライアントに対してのみリソースを提供する
© 2016 Naohiro Fujie 18
構成要素
• 保護対象リソース• API を経由して提供される機能やデータなどのリソース
• リソース・オーナー• 保護対象となるリソースの持ち主(ユーザ)
• クライアント• 保護対象リソースへアクセスする主体(API クライアント)
• 認可サーバ• 保護対象リソースへのアクセスを認可するためのサーバ
© 2016 Naohiro Fujie 19
保護対象リソース(API 、データ)
各構成要素の関係性
© 2016 Naohiro Fujie 20
クライアント
認可サーバ
リソース・オーナー
所有
登録
認可 利用
登録
保護利用
保護対象リソース(API 、データ)
実際の動き(認可コードグラント)
© 2016 Naohiro Fujie 21
クライアント
認可サーバ
リソース・オーナー
所有
登録
認可 利用
登録
保護利用
1.利用したい
2.認可要求
3.認可要求(同意)
4.認可コードの発行
5.認可コードの提供
6.認可コードとアクセストークン
の交換
8.アクセストークンの検証
7.アクセストークンの提示
9.リソースの提供
シーケンス(認可コードグラント)
© 2016 Naohiro Fujie 22
リソース・オーナー(利用者/UserAgent)
クライアント(アプリケーション)
認可サーバ(OAuthサーバ)
保護対象リソース(API・データ)
フロー開始
ユーザ認証・同意
認可エンドポイントへリダイレクト
(クライアントID、スコープを含む)
クライアントへリダイレクト
(認可コードを含む)アクセストークンを要求(認可コード、クライアントID、Secretを含む)
アクセストークンを発行
リソース要求(アクセストークンを含む)
アクセストークンの検証
リソースの提供
クライアントに許可する範囲(スコープ)指定が可能
オーナー(ユーザ)の同意に基づくアクセスが可能
許可されたクライアントのみからアクセスする為の認証
パスワードではなく、アクセストークンでリソース利用
アクセストークンから利用者を確認(JWTの場合)
ポイント
• スコープの指定とオーナーによる同意• スコープが変わると再同意とアクセス・トークンの再取得が必要
• 登録されたクライアントだけにトークンを発行• クライアントIDとクライアントSecretによる認証
• 期限付きのコード・トークンを利用(パスワードの保存なし)• 認可コード
• アクセス・トークンとの交換用(ごく短時間有効)• アクセス・トークン
• リソースへのアクセスに利用(短時間有効)• リフレッシュ・トークン
• アクセス・トークンとの交換用(それなりの期間有効)• アクセス・トークンの期限切れが発生した際に再度オーナーのインタラクションが無しにアク
セス・トークンを再取得
• アクセス・トークンの使い方により利用ユーザの特定も可能• JWT(JSON Web Token)を利用する場合(Azure AD はこれ)
© 2016 Naohiro Fujie 23
デモAzure Active Directory による WebAPI の保護
Active Directory Authentication Library による API クライアントの実装
© 2016 Naohiro Fujie 24
保護対象リソース(ASP.NET WebAPI)
デモ構成
© 2016 Naohiro Fujie 25
クライアント(ASP.NET MVC
+ADAL)
認可サーバ(Azure AD)
リソース・オーナー(Azure AD 利用者)
所有
登録
認可 利用
登録
保護利用
Azure AD の OAuth と ADAL
• Azure AD(Azure Active Directory)の OAuth• Facebook などコンシューマの OAuth 実装との差は「Azure AD 以外のリソース」へのアクセスを認可する点※Facebook などは自身が OAuth サーバでありリソースサーバ
• そのため、Resource パラメータが必須となっている
• ADAL(Active Directory Authentication Library)• Azure AD、AD FS 向けの認証ライブラリ• .NET 以外にも Java や JavaScript、Ruby、objective-C などに対応• OAuth、OpenID Connect、ws-federation への対応• トークンのキャッシュの自動管理(必要に応じてリフレッシュ・トー
クンでアクセス・トークンを再取得)など、超便利
© 2016 Naohiro Fujie 26
WebAPI の作成
© 2016 Naohiro Fujie 27
プロジェクトの作成
© 2016 Naohiro Fujie 28
Web API を選択
© 2016 Naohiro Fujie 29
Azure ADドメインを指定
© 2016 Naohiro Fujie 30
完成
© 2016 Naohiro Fujie 31
Azure ADに自動で登録される
© 2016 Naohiro Fujie 32
※テスト用)非HTTPS化しておく
© 2016 Naohiro Fujie 33
API Client の作成
© 2016 Naohiro Fujie 34
プロジェクトの作成
© 2016 Naohiro Fujie 35
MVC を選択
© 2016 Naohiro Fujie 36
Azure ADドメインを指定
© 2016 Naohiro Fujie 37
完成
© 2016 Naohiro Fujie 38
Azure ADに自動で登録される
© 2016 Naohiro Fujie 39
取り敢えず実行するとユーザ認証
© 2016 Naohiro Fujie 40
プロファイル取得に関する同意(Azure AD リソースへのアクセス)
© 2016 Naohiro Fujie 41
サインイン完了
© 2016 Naohiro Fujie 42
API アクセス
© 2016 Naohiro Fujie 43
API Client ⇒ WebAPI へアクセス許可
© 2016 Naohiro Fujie 44
登録された WebAPI を選択
© 2016 Naohiro Fujie 45
スコープを選択
© 2016 Naohiro Fujie 46
許可設定
© 2016 Naohiro Fujie 47
API Client のキーを生成(client_secret)
© 2016 Naohiro Fujie 48
保存するとキーが表示される
© 2016 Naohiro Fujie 49
nuget で ADAL をインストール
© 2016 Naohiro Fujie 50
Web.config に必要な情報を入れる
© 2016 Naohiro Fujie 51
Web.config に設定する情報
• appSetting に以下を追加• Azure AD ポータルで取得したAPI Client のキー
• <add key="ida:ClientSecret" value="WrVNTezVxZBjXPQBEMQ6VhKuJ9Ell1+An1cLCHZGmAo=" />
• アクセス先の API の識別子(リソースID)• <add key="ida:ResourceId"
value="https://pharaoh.onmicrosoft.com/testWebAPI" />
© 2016 Naohiro Fujie 52
Startup.Auth へのコード追加
• Using ディレクティブ• using Microsoft.IdentityModel.Clients.ActiveDirectory;
• OpenIdConnectAuthenticationOptions// WebAPIリソースへのアクセスを要求Resource = ConfigurationManager.AppSettings["ida:ResourceId"],Notifications = new OpenIdConnectAuthenticationNotifications{
// ADALのトークンキャッシュにアクセストークンを乗せるため、認可コードでアクセストークンを取得AuthorizationCodeReceived = async (context) =>{
string code = context.Code;AuthenticationContext authContext = new AuthenticationContext(authority);AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
code,new Uri(postLogoutRedirectUri),new ClientCredential(
clientId, ConfigurationManager.AppSettings["ida:ClientSecret"]));
}}
© 2016 Naohiro Fujie 53
HomeController へのコード追加
• Using ディレクティブ• using System.Configuration;
• using Microsoft.IdentityModel.Clients.ActiveDirectory;
• using System.Net.Http;
• using System.Net.Http.Headers;
© 2016 Naohiro Fujie 54
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response =
httpClient.GetAsync("http://localhost:1867/api/values").Result;
if (response.IsSuccessStatusCode){
ViewBag.Message =
response.Content.ReadAsStringAsync().Result;
}
} catch (AdalException ex) {
ViewBag.Message = ex.Message;
}
return View();
}
public async System.Threading.Tasks.Task<ActionResult> About()
{
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
string authority = aadInstance + tenantId;
var resourceId = ConfigurationManager.AppSettings["ida:ResourceId"];
try
{
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult result =
await authContext.AcquireTokenSilentAsync(
resourceId,
credential,
UserIdentifier.AnyUser);
© 2016 Naohiro Fujie 55
API Client を起動すると WebAPI へのアクセスについて同意を求められる
© 2016 Naohiro Fujie 56
サインイン完了
© 2016 Naohiro Fujie 57
WebAPI を実行
© 2016 Naohiro Fujie 58
参考資料
• 脱オンプレミス! クラウド時代の認証基盤 Azure Active Directory 完全解説• 著 : Vittorio Bertocci
• 監訳 : 安納 順一、富士榮 尚寛
• ¥3,996
• Kindle 版もあり〼
• http://amzn.to/2h01o2h
© 2016 Naohiro Fujie 59
まとめ
• OAuth を使って安全に API を使いましょう• 利用者にとって
• パスワードの氾濫を防ぐ
• 権限の絞り込みを適切に行う
• API 提供者にとって• 適切な API クライアントへ提供する
• Azure AD、ADAL を使うと結構簡単に実装できます。• 本買ってね
© 2016 Naohiro Fujie 60