Share
Embed
Go言語で学ぶYahoo! ID連携の黒帯ハンズオン / 20200204-kurobi-hands-on
ID連携の入門者向けのOpenID Connectの解説とハンズオンの資料です。
OpenID Connectはユーザーの認証と認可を連携するためのID連携の仕組みです。OAuth 2.0を拡張した仕様であり、HTTP通信やJSONなど基礎的なWeb技術によって構成されています。
ヤフーが提供するYahoo! ID連携を用いてGo言語でID連携を実装します。実装の注意点とそのリスク、仕様に施されているセキュリティ対策についてハンズオンを行いながら解説します。
ID連携に興味がある方、Go言語でOAuth 2.0やOpenID Connectをつないでみたい方などぜひ資料をご参照ください。
kura
February 04, 2020
Transcript
157 slides
-
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 2020年2⽉4⽇
ヤフー株式会社 倉林 雅 ⿊帯ハンズオン 〜認証技術〜 Go⾔語で学ぶYahoo! ID連携の⿊帯ハンズオン -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. プロフィール
2 倉林 雅(くらはやし まさる) ヤフー株式会社 認証技術⿊帯 / ID・セキュリティエンジニア OpenID ファウンデーション・ジャパン 理事 / エバンジェリスト -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 3
https://twitter.com/kura_lab -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. ⿊帯制度
4 該当分野について突出した知識とスキルを 持っている第⼀⼈者 ⿊帯制度 http://hr.yahoo.co.jp/workplace/culture.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. アジェンダ
5 1. OpenID Connectの概要説明 2. Go⾔語によるYahoo! ID連携の実装 3. 実装のポイント、セキュリティ対策の解説 4. まとめ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID Connectの概要説明 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 7
OpenID Connectの提供する機能 簡単にいうと 「ユーザー認証」と「属性情報取得」 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 8
所持しているIDのサービスを選択 ログインをする -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 9
同意をする 情報がプリセットされる -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. ⽤語集
10 IdP (Identity Provider) • 認証を⾏ってID、属性情報を提供する RP (Relying Party) • IdPの認証を利⽤してEnd-Userにサービスを提供する Claim • IdPやEnd-Userなどの属性情報 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenIDとは
11 • OpenIDはユーザーの認証の技術 • (OpenID AXで属性を取得する拡張はある) • XML・SOAPベースのプロトコル -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID
12 2006年 • OpenID Authentication 1.1 • OpenID Simple Registration Extension 1.0 2007年 • OpenID Authentication 2.0 • OpenID Attribute Exchange 1.0 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OAuth
13 • OAuth 1.0とOAuth 2.0はユーザーの認可の技術 • ユーザーのリソースアクセス(Web API)が⽬的 • JSONとRESTライクのプロトコル -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OAuth
14 2009年 • OAuth 1.0a 2010年 • OAuth 1.0 Protocol 2012年 • OAuth 2.0 Authorization Framework オーオースッ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID
Connectとは 15 • ユーザーの認証と認可を兼ね備えた技術 • OpenIDとは異なる技術でOAuth 2.0を拡張した プロトコル • JSONとRESTライクのプロトコル • 2014年2⽉にローンチ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 16
https://openid.net/connect/ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 17
https://openid.net/connect/ OpenID ConnectはOAuth 2.0や JSON Web Tokenなどの技術で構成されている -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID
Connect 18 Web API連携・ユーザー認証・ ユーザー属性情報取得に必要な仕様が定義れている 認可 認証 属性取得 OAuth 2.0 JSON Web Token -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID
Connect 19 • OpenID Connectには多様なユースケースに適 応できるよう3つのフローが定義されている 1. Implicit Flow 2. Authorization Code Flow 3. Hybrid Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Implicit
Flow 20 • 主にスクリプト⾔語を⽤いて実装されブラウザ上で 動作するClientによって使⽤される • Access TokenとID Tokenは、Clientに直接返却され End-UserとEnd-UserのUser Agentにアクセスする アプリケーションに露出する可能性がある • Authorization Serverはクライアント認証を⾏わない -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 21
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 6.Access Token/ID Token 8.Access Token 9.Claims 4.同意画⾯ 10.属性情報取得完了 7.ログイン完了 Implicit Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Authorization
Code Flow 22 • ClientにAuthorization Codeを返却し、Clientはそれを 直接ID TokenおよびAccess Tokenと交換するため、 User AgentやUser Agent上の他の不正アプリケーショ ンなどにトークンが露呈することがない • Authorization Serverは、Authorization Codeを Access Tokenと交換する前にClientを認証することも できる • Client SecretをClientとAuthorization Serverの間でセ キュアに保てるClientに適している -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 23
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 11.Access Token 12.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 13.属性情報取得完了 9.ログインセッション 発⾏ 10.ログイン完了 Authorization Code Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Hybrid
Flow 24 • Authorization EndpointからAccess TokenとID Token がブラウザー上のClientに直接返却される • さらにAuthorization Codeが返却され、Token EndpointでAccess TokenとID TokenがBack-End Serverに返却される -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 25
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 12.Access Token/ID Token 15.Access Token 16.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code/Access Token/ID Token 11.Tokenリクエスト(Authorization Code) 17.属性情報取得完了 13.ログインセッション 発⾏ 14.ログイン完了 10.Authorization Code 8.Access Token 7.クライアントサイド ログイン完了 9.クライアントサイド 属性情報取得完了 Hybrid Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Hybrid
Flowを導⼊する前に 26 • Authorization Codeをサーバーへ送信する前に、 Clientサイドでユーザー認証をいち早く完了させたい場 合などに利⽤できる • ただし、⼤抵の3rd PartyアプリケーションはID管理を Back-End Serverで⾏うため、Clientサイドのユー ザー認証は不要である • Hybrid Flowを導⼊する前に、Authorization Code FlowでID連携の要件を満たすことができるか検討すべ きである -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. Go⾔語によるYahoo! ID連携の実装 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 0.
事前準備 • Go実⾏環境セットアップ • 以下の⼿順に従い環境を設定し動作確認をしてください。 • https://github.com/kura-lab/kuroobi-hands-on- 2020/tree/20200204/practice00 • 今回は「go1.13.7」のバージョンを使⽤します。 注意点 2020年2⽉4⽇時点の演習のソースコードは 「master」ブランチではなく「20200204」ブランチになっています。 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 0.
事前準備 • OpenID Connectを提供しているYahoo! JAPANの Yahoo! ID連携 v2を利⽤します。 • Yahoo! ID連携とは • https://developer.yahoo.co.jp/yconnect/v2/introduction.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 0.
事前準備 30 • 以下のサイトの1〜3のステップに従いClient IDを登録する。 • Yahoo! ID連携 v2 > 利⽤までのステップ • https://developer.yahoo.co.jp/yconnect/v2/ • 今回は「サーバーサイド(Yahoo! ID連携 v2)」のClient IDを 使って「Authorization Code Flow」をGo⾔語で実装します。 • Client IDとClient Secretを発⾏してしてください。 • コールバックURLに「http://localhost:8080/callback」を 登録してください。 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 1.
Authorizationリクエスト 31 • RP(Client)からUserAgentを利⽤してIdP (AuthN/AuthZ Server)へリクエストする • IdPのID(Yahoo! JAPAN ID)でログインし、ユー ザー同意画⾯を表⽰し同意を取得する https://github.com/kura-lab/kuroobi-hands-on-2020/tree/20200204/practice01 演習ソースコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 32
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ログイン</title> <meta name="description" content=""> <meta name="author" content=""> <link rel="stylesheet" href=""> <link rel="icon" href="data:,"> </head> <body> <script> </script> <a href="{{.}}">ログイン</a> </body> </html> templates/index.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 33
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ログイン</title> <meta name="description" content=""> <meta name="author" content=""> <link rel="stylesheet" href=""> <link rel="icon" href="data:,"> </head> <body> <script> </script> <a href="{{.}}">ログイン</a> </body> </html> templates/index.html テンプレートへ渡される値の プレースホルダー -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 34
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ログイン完了</title> <meta name="description" content=""> <meta name="author" content=""> <link rel="stylesheet" href=""> <link rel="icon" href="data:,"> </head> <script> </script> ... templates/callback.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 35
... <body> ログイン完了<br/> ユーザー識別⼦︓{{ .Subject }}<br/> メールアドレス︓{{ .Email }}<br/> <a href="/index">戻る</a> </body> </html> templates/callback.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 36
... <body> ログイン完了<br/> ユーザー識別⼦︓{{ .Subject }}<br/> メールアドレス︓{{ .Email }}<br/> <a href="/index">戻る</a> </body> </html> templates/callback.html テンプレートへ渡される構造体の 変数を参照 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 37
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>エラー</title> <meta name="description" content=""> <meta name="author" content=""> <link rel="stylesheet" href=""> <link rel="icon" href="data:,"> </head> <body> <script> </script> {{.}} </body> </html> templates/error.html -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 38
func main() { // 1-1. マルチプレクサにハンドラを登録 mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("http://localhost:%d/index", config.Port), http.StatusMovedPermanently) }) mux.HandleFunc("/index", index) mux.HandleFunc("/callback", callback) ... practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 39
func main() { // 1-1. マルチプレクサにハンドラを登録 mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("http://localhost:%d/index", config.Port), http.StatusMovedPermanently) }) mux.HandleFunc("/index", index) mux.HandleFunc("/callback", callback) ... practice01/server.go アクセスのあったURLに合わせて ルーティング -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 40
... // 1-2. サーバー設定 server := &http.Server{ Addr: fmt.Sprintf("0.0.0.0:%d", config.Port), Handler: mux, ReadTimeout: time.Second * 10, WriteTimeout: time.Second * 600, MaxHeaderBytes: 1 << 20, // 1MB } err := server.ListenAndServe() if err != nil { log.Fatal(err) } } practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 41
... const ( Port = 8080 ) ... // 1-3. Client ID、Client Secretを定義 const ( ClientID = "<CLIENT_ID>" ClientSecret = "<CLIENT_SECRET>" ) // 1-4. リダイレクトURIを定義 var RedirectURI = fmt.Sprintf("http://localhost:%d/callback", Port) // 1-5. OpenID ConnectのURLを定義 const ( OIDCURL = "https://auth.login.yahoo.co.jp" ) ... config/config.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 42
... const ( Port = 8080 ) ... // 1-3. Client ID、Client Secretを定義 const ( ClientID = "<CLIENT_ID>" ClientSecret = "<CLIENT_SECRET>" ) // 1-4. リダイレクトURIを定義 var RedirectURI = fmt.Sprintf("http://localhost:%d/callback", Port) // 1-5. OpenID ConnectのURLを定義 const ( OIDCURL = "https://auth.login.yahoo.co.jp" ) ... config/config.go ⼤⽂字はじまりの変数︓Public変数 ⼩⽂字はじまりの変数︓Private変数 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 43
... import ( ... // 1-6. 設定パッケージのインポート "github.com/kura-lab/kuroobi-hands-on-2020/config" ) ... practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 44
... // 1-7. テンプレートをレンダリング var ( indexTemplate = template.Must(template.ParseFiles(”../templates/index.html")) callbackTemplate = template.Must(template.ParseFiles("../templates/callback.html")) errorTemplate = template.Must(template.ParseFiles("../templates/error.html")) ) ... practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 45
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) Authorization Code Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 46
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 0-2.処理開始 Authorization Code Flow Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 47
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 0-2.処理開始 Authorization Code Flow RPからIdPへ認証・認可をUser Agentを利⽤して GETのリダイレクト(もしくはPOST)でリクエスト Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 48
HTTP/1.1 302 Found Location: https://auth.login.yahoo.co.jp/yconnect/v2/authorization? client_id=dj00aiZpPXVlM...& response_type=code& scope=openid+email& redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback& state=abc& nonce=xyz& prompt=consent -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 49
// AuthorizationリクエストのURLを⽣成 func index(w http.ResponseWriter, r *http.Request) { // 1-8. AuthorizationリクエストURL⽣成 u, err := url.Parse(config.OIDCURL) if err != nil { // 1-9. 構造体にエラー⽂⾔を格納してerror.htmlをレンダリング w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "url parse error") return } u.Path = path.Join(u.Path, "yconnect/v2/authorization") q := u.Query() ... practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 50
// AuthorizationリクエストのURLを⽣成 func index(w http.ResponseWriter, r *http.Request) { // 1-8. AuthorizationリクエストURL⽣成 u, err := url.Parse(config.OIDCURL) if err != nil { // 1-9. 構造体にエラー⽂⾔を格納してerror.htmlをレンダリング w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "url parse error") return } u.Path = path.Join(u.Path, "yconnect/v2/authorization") q := u.Query() ... practice01/server.go テンプレートの プレースホルダーに渡す値 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 51
... // 1-10. response_typeにAuthorization Code Flowを指定 q.Set("response_type", "code") q.Set("client_id", config.ClientID) q.Set("redirect_uri", config.RedirectURI) // 1-11. UserInfoエンドポイントから取得するscopeを指定 q.Set("scope", "openid email") // 1-12. ログイン画⾯と同意画⾯の強制表⽰ q.Set("prompt", "login consent") q.Set("nonce", "NONCE_STUB") u.RawQuery = q.Encode() // 1-13. 構造体にURLをセットしindex.htmlをレンダリング w.WriteHeader(http.StatusOK) indexTemplate.Execute(w, u.String()) } practice01/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 52
... // 1-10. response_typeにAuthorization Code Flowを指定 q.Set("response_type", "code") q.Set("client_id", config.ClientID) q.Set("redirect_uri", config.RedirectURI) // 1-11. UserInfoエンドポイントから取得するscopeを指定 q.Set("scope", "openid email") // 1-12. ログイン画⾯と同意画⾯の強制表⽰ q.Set("prompt", "login consent") q.Set("nonce", "NONCE_STUB") u.RawQuery = q.Encode() // 1-13. 構造体にURLをセットしindex.htmlをレンダリング w.WriteHeader(http.StatusOK) indexTemplate.Execute(w, u.String()) } practice01/server.go response_typeの指定によってフローがわかる response_typeの値 Flow code Authorization Code id_token Implicit id_token token Implicit code id_token Hybrid code token Hybrid code id_token token Hybrid -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 53
... // 1-10. response_typeにAuthorization Code Flowを指定 q.Set("response_type", "code") q.Set("client_id", config.ClientID) q.Set("redirect_uri", config.RedirectURI) // 1-11. UserInfoエンドポイントから取得するscopeを指定 q.Set("scope", "openid email") // 1-12. ログイン画⾯と同意画⾯の強制表⽰ q.Set("prompt", "login consent") q.Set("nonce", "NONCE_STUB") u.RawQuery = q.Encode() // 1-13. 構造体にURLをセットしindex.htmlをレンダリング w.WriteHeader(http.StatusOK) indexTemplate.Execute(w, u.String()) } practice01/server.go ログイン画⾯(login)、同意画⾯(consent)の表⽰を強制 しt礼しないと不要な場合は省略される -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 54
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 4.同意画⾯ 0-2.処理開始 Authorization Code Flow IdPがログイン・同意をユーザーに要求 Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 2.
Tokenリクエスト 55 • Authorizationリクエストを正常に終えると Authorization Code値が返却される • Authorization Code値をIdP(AuthN/AuthZ Server) へリクエストし、Access TokenとRefresh Token、ID Tokenを取得する https://github.com/kura-lab/kuroobi-hands-on-2020/tree/20200204/practice02 演習ソースコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 56
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 4.同意画⾯ 0-2.処理開始 6.Authorization Code Authorization Code Flow ユーザーが認可した情報が Authorization Codeとして User Agentを通じて Back-End Serverへ返却される Access Token等を 直接Clientサイドへ返却せず 有効期限の短い⼀時トークンとして Authorization Codeを 返却するためImplicit Flowよりも強固 Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 57
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) Authorization Code Flow Authorization Codeに加えてサーバー間で Client IDとClient Secretによるクライアント認証を ⾏うためImplicit Flowよりも強固 Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 58
POST /yconnect/v2/token HTTP/1.1 Host: auth.login.yahoo.co.jp Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&client_id=djsaWss2ka6sn...& client_secret=inlakw1cqa2asCcr...& code=SplxlOBeZQQYbYS6WxSbIA& redirect_uri=https%3A%2F%2Flocalhoost%3A8080%2Fcallback -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 59
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) Authorization Code Flow 認可のAccess Tokenと IdPが⾏ったユーザー認証の 情報を含むID Tokenを返却 Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 60
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 9.ログインセッション 発⾏ 10.ログイン完了 Authorization Code Flow Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 61
HTTP/1.1 200 OK Cache-Control: private, no-store, no-cache, must-revalidate Pragma: no-cache Content-Type: application/json { "access_token":"KyTCfK8VtfW...", "token_type":"Bearer", "expires_in":3600, "id_token":"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3Mi OiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEjXk", "refresh_token":"gE3tiP5nPp7...” } -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 62
// Access Tokenの取得、ID Tokenの取得と検証 // UserInfoエンドポイントからユーザー属性情報の取得 func callback(w http.ResponseWriter, r *http.Request) { // 2-1. クエリを取得 query := r.URL.Query() ... // 2-2. Tokenリクエスト values := url.Values{} values.Set("grant_type", "authorization_code") values.Add("client_id", config.ClientID) values.Add("client_secret", config.ClientSecret) values.Add("redirect_uri", config.RedirectURI) ... practice02/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 63
// Access Tokenの取得、ID Tokenの取得と検証 // UserInfoエンドポイントからユーザー属性情報の取得 func callback(w http.ResponseWriter, r *http.Request) { // 2-1. クエリを取得 query := r.URL.Query() ... // 2-2. Tokenリクエスト values := url.Values{} values.Set("grant_type", "authorization_code") values.Add("client_id", config.ClientID) values.Add("client_secret", config.ClientSecret) values.Add("redirect_uri", config.RedirectURI) ... practice02/server.go grant_typeでAuthorization Codeの値を渡すことを指定 refresh_tokenを指定することでAccess Tokenの更新もできる -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 64
... // 2-3. redirect_uriからAuthorization Codeを抽出 values.Add("code", query["code"][0]) tokenResponse, err := http.Post(config.OIDCURL+"/yconnect/v2/token", "application/x-www-form-urlencoded", strings.NewReader(values.Encode())) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to post request") return } ... practice02/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 65
... defer func() { _, err = io.Copy(ioutil.Discard, tokenResponse.Body) if err != nil { log.Panic(err) } err = tokenResponse.Body.Close() if err != nil { log.Panic(err) } }() ... practice02/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 66
... defer func() { _, err = io.Copy(ioutil.Discard, tokenResponse.Body) if err != nil { log.Panic(err) } err = tokenResponse.Body.Close() if err != nil { log.Panic(err) } }() ... practice02/server.go Discardで書き込んだバイトを捨てる -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 67
... // 2-4. TokenエンドポイントのJSONレスポンスの結果を格納する構造体 type TokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` IDToken string `json:"id_token"` } ... practice02/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 68
... // 2-5. Tokenレスポンスを構造体に格納 var tokenData TokenResponse err = json.NewDecoder(tokenResponse.Body).Decode(&tokenData) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to read token's json body") return } log.Println("requested token endpoint") ... practice02/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 3.
UserInfoリクエスト 69 • Tokenリクエストで取得したAccess TokenをUserInfo エンドポイントへリクエストする • IdPに登録されているユーザーの属性情報を取得する https://github.com/kura-lab/kuroobi-hands-on-2020/tree/20200204/practice03 演習ソースコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. UserInfoエンドポイント
70 • ⽒名や住所、メールアドレスなどの属性情報を標準仕様 化して取得しやすいように属性情報(Claim)を定義 • 関連した属性情報ごとにアクセス制限としてScopeを定 義 • RPのサービスに必要な属性情報だけをIdPに要求するこ とが可能 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 定義されている属性情報
71 Claim Scope 説明 sub - ユーザー識別⼦ name profile ⽒名 give_name profile 名 family_name profile 姓 middle_name profile ミドルネーム nickname profile ニックネーム preferred_ username profile 簡略名 Claim Scope 説明 profile profile プロフィール情報の URL picture profile プロフィール画像の URL website profile サイトURL gender profile 性別 birthdate profile ⽣年⽉⽇ email email メールアドレス email_verified email メールアドレスの 検証済みの有無 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 定義されている属性情報
72 Claim Scope 説明 zoneinfo profile タイムゾーン locale profile 国コード update profile 属性情報更新⽇時 phone_number phone 電話番号 phone_number _verified phone 電話番号の検証済み の有無 Claim Scope 説明 address/country address 国コード address/postal_code address 郵便番号 address/region address 都道府県 address/locality address 市区町村 address/formatted address 住所 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. ユーザー属性情報取得の留意点
73 • 全Scopeを指定すればすべての属性情報を取得すること ができる • 関連した属性情報ごとにScopeがわけられているのは、 サービスに必要なものに絞って取得できるにしている • 不要な属性情報の取得はユーザーの不安につながるため 注意が必要 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 74
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 11.Access Token 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 9.ログインセッション 発⾏ 10.ログイン完了 Authorization Code Flow 属性情報の取得が必要な場合は UserInfoエンドポイントへ Access Tokenを送信 Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 75
GET /yconnect/v2/attribute HTTP/1.1 Host: auth.login.yahoo.co.jp Authorization: Bearer SlAV32hkKG... -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 76
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 11.Access Token 12.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 13.属性情報取得完了 9.ログインセッション 発⾏ 10.ログイン完了 Authorization Code Flow 各属性情報がJSONで返却される Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 77
HTTP/1.1 200 OK Content-Type: application/json { “sub”: “YIZ7F3ZBD...”, “name”: “倉林 雅”, “given_name”: “雅”, “family_name”: “倉林”, “nickname”: “kura”, “picture”: “https://giwiz-display-name.c.yimg.jp/d/iwiz-display- name/...”, “email”: “[email protected]” } -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 78
... // 3-1. UserInfoリクエスト userInfoRequest, err := http.NewRequest( "POST", "https://userinfo.yahooapis.jp/yconnect/v2/attribute", nil, ) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to create user attribute request") return } ... practice03/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 79
... userInfoRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") userInfoRequest.Header.Set("Authorization", "Bearer "+tokenData.AccessToken) userInfoResponse, err := http.DefaultClient.Do(userInfoRequest) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to user attribute request") return } ... practice03/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 80
... userInfoRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") userInfoRequest.Header.Set("Authorization", "Bearer "+tokenData.AccessToken) userInfoResponse, err := http.DefaultClient.Do(userInfoRequest) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to user attribute request") return } ... practice03/server.go OAuth 2.0 Bearer Token 認可されたAccess Tokenを指定 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 81
... defer func() { _, err = io.Copy(ioutil.Discard, userInfoResponse.Body) if err != nil { log.Panic(err) } err = userInfoResponse.Body.Close() if err != nil { log.Panic(err) } }() ... practice03/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 82
... // 3-2. UserInfoエンドポイントのJSONレスポンスの結果を格納する構造体 type UserInfoResponse struct { Subject string `json:"sub"` Email string `json:"email"` } ... practice03/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 83
... // 3-3. UserInfoレスポンスを構造体に格納 var userInfoData UserInfoResponse err = json.NewDecoder(userInfoResponse.Body).Decode(&userInfoData) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to parse user info json") return } log.Println("requested userinfo endpoint") ... // 3-4. 構造体にユーザー属性情報をセットしcallback.htmlをレンダリング w.WriteHeader(http.StatusOK) callbackTemplate.Execute(w, userInfoData) log.Println("[[ login completed ]]") ... practice03/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. 実装のポイント、セキュリティ対策の解説 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. CSRF
(Cross-Site Request Forgery) 85 ⼀般的な場合 • 別のサイトに⽤意したコンテンツ上の罠のリンクを踏ま せることなどをきっかけとして、インターネットショッ ピングの最終決済や退会などのWebアプリケーション の重要な処理を呼び出すようユーザーを誘導する攻撃 OAuth 2.0/OpenID Connectの場合 • 悪意あるユーザーの認可コードを被害者に乗っ取らせ情 報を窃取する -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 86
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 9.Access Token 10.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 11.属性情報取得 ログイン完了 Authorization Code Flow CSRFの 攻撃対象になる部分は どこでしょう︖ Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 87
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 9.Access Token 10.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 11.属性情報取得 ログイン完了 Authorization Code Flow 他ユーザーのAuthorization Codeに 置き換えができてしまう Resource Server (UserInfo) 対策なしのままではAuthorizationリクエストから Authorization Codeを受け取るまで セッションが同⼀であることを保証できない -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 88
Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 9.Access Token 10.Claims 4.同意画⾯ 0-2.処理開始 6.悪意あるユーザーのAuthorization Code 7.Tokenリクエスト(Authorization Code) 11.属性情報取得 ログイン完了 Authorization Code Flow 悪意あるユーザーが⾃⾝のIDで ログイン、同意を⾏いBE Serverへ送信せず Authorization Codeを取得する Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 89
Client (User Agent) Back-End Server AuthN/AuthZ Server 11.Access Token/ID Token 12.Access Token 13.Claims 10.Tokenリクエスト(Authorization Code) 14.属性情報取得 ログイン完了 End-User 7.redirect_uri 8.URLクリック 9.悪意あるユーザーの Authorization Code Authorization Code Flow 悪意あるユーザーのIDで取得した Access Tokenを含んだredirect_uriを フィッシングサイトなどでアクセスさせる 乗っ取らせてサービスを利⽤させ ユーザーの情報を窃取する Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. state
90 • OAuth 2.0にはCSRF対策として「state」パラメーター が定義されている • RPがAuthorizationリクエストで指定したstate値と Authorizationレスポンスで認可コードと同時に返却さ れるstate値の⼀致を検証することで認可コードの置き 換えに夜乗っ取らせを防⽌する -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 91
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト(state=xyz) 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token 9.Access Token 10.Claims 4.同意画⾯ 0-2.処理開始 6.Authorization Code + state=xyz 7.Tokenリクエスト(Authorization Code) 11.属性情報取得 ログイン完了 Authorization Code Flow RPのセッション (HTTPOnlyのCookieなど) に紐付けたstate値を送信 Authorization Codeと共にstate値が返却される セッションに紐づけたstate値と⼀致するか 検証することでCSRFを防⽌ Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 92
Client (User Agent) Back-End Server AuthN/AuthZ Server 11.Access Token/ID Token 12.Access Token 13.Claims 10.Tokenリクエスト(Authorization Code) 14.属性情報取得 ログイン完了 End-User 7.redirect_uri 8.URLクリック 9.悪意あるユーザーの Authorization Code + state=abc Authorization Code Flow 被害者のセッションに紐づくstate値 (サービスのAuthorizationリクエストに アクセスしていなければセッションもない) と⼀致しないため置き換えを検知できる Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 4.
CSRF対策(state値の検証) 93 • Authorizationレスポンスで返却されるAuthorization Code値の置き換えによるCSRF攻撃を対策する • RPで⽣成したstate値をセッションCookieに紐付ける • state値をAuthorizationリクエストで送信する • 返却されたstate値がとセッションCookieに紐付けた state値と同⼀であるかを検証する https://github.com/kura-lab/kuroobi-hands-on-2020/tree/20200204/practice04 演習ソースコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 94
... // 4-1. ランダム⽂字列を⽣成 func init() { rand.Seed(time.Now().UnixNano()) } var randLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") func generateRandomString() string { result := make([]rune, 32) for i := range result { result[i] = randLetters[rand.Intn(len(randLetters))] } return string(result) } ... practice04/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 95
... // 4-2. セッションCookieに紐付けるstate値を⽣成し保存 state := generateRandomString() stateCookie := &http.Cookie{ Name: "state", Value: state, HttpOnly: true, } http.SetCookie(w, stateCookie) ... // 4-3. セッションCookieに紐づけたstate値を指定 q.Set("state", state) ... practice04/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 96
... // 4-2. セッションCookieに紐付けるstate値を⽣成し保存 state := generateRandomString() stateCookie := &http.Cookie{ Name: "state", Value: state, HttpOnly: true, } http.SetCookie(w, stateCookie) ... // 4-3. セッションCookieに紐づけたstate値を指定 q.Set("state", state) ... practice04/server.go 今回はstateというフィールド名で Cookieを発⾏ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 97
... // 4-4. redirect_uriからstate値の抽出 stateQuery, ok := query["state"] if !ok { w.WriteHeader(http.StatusBadRequest) errorTemplate.Execute(w, "state query not found") return } state := stateQuery[0] storedState, err := r.Cookie("state") if err != nil { w.WriteHeader(http.StatusBadRequest) errorTemplate.Execute(w, "state cookie error") return } ... practice04/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 98
... // 4-5. セッションCookieに紐づけていたstate値の破棄 stateCookie := &http.Cookie{ Name: "state", MaxAge: -1, } http.SetCookie(w, stateCookie) // 4-6. state値の検証 if state != storedState.Value { w.WriteHeader(http.StatusBadRequest) errorTemplate.Execute(w, "state does not match stored one") return } log.Println("success to verify state") ... practice04/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. ID
Tokenとは 99 • ユーザー認証情報を含む改ざん検知⽤の署名付きToken • JSON Web Token(JWT)フォーマット • IdPが認証したユーザーの認証情報を含めRPが検証し RP側のセッション管理に⽤いる ID -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. JSON
Web Token(JWT) 100 • JSONをBase64urlエンコード(URL SafeなBase64エン コード)したシグネチャ(ハッシュ値もしくはデジタル署 名)付きトークン • ヘッダー・ペイロード・シグネチャの3つから構成される • シグネチャはハッシュ(HMAC)と 公開鍵暗号(RSA・ECDSA)をサポート • JWTと表記して「jot(ジョット)」と発⾳する Jot down -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 101
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleH AiOjEzMDA4MTkzODAsDQogImh0dHA 6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp 0cnVlfQ.dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEj Xk ID Token -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 102
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzM DA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEj Xk Header Payload Signature ピリオド区切りの3つの部位から構成される -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 103
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzM DA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEj Xk Header Payload Signature Base64エンコードは「/」や「=」が 含まれURL Safeでないため Base64urlエンコードが利⽤されている -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 104
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9 { "type": "JWT", "alg": "RS256" } Header Signature Base64urlデコード 置換前 置換後 “-” “+” “/” “_” (データ⻑ % 4)の 数だけ”=”をパディング Base64urlデコード Base64デコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 105
{ "type": "JWT", "alg": "RS256" } type: JSON Web Token -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 106
{ "type": "JWT", "alg": "RS256" } algorithm: Signatureのアルゴリズム RS256=RSA SHA-256 alg アルゴリズム 実装要求 HS256 HMAC SHA-256 RS256 RSA SHA-256 推奨 ES256 ECDSA SHA-256 推奨 JWT サポートアルゴリズム -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 107
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzM DA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEj Xk Header Payload Signature Header + ”.” + Payloadを ⼊⼒データとする -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 108
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIU zI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzM DA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP- mB92K27uhbUJU1p1r_wW1gFWFOEj Xk Header Payload Signature 検証結果が正しければ PayloadのClaim(属性情報)を参照する typのアルゴリズムで⼊⼒データとSignatureと IdPが公開している公開鍵をつかって改ざんを検証 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 109
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzM DA4MTkzODAsDQogImh0dHA6Ly9leG FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ { "iss":"https://idp.example.com", "sub":"123456789", "aud":"abcdefg", "nonce":"xyz", "iat":1291836800, "exp":1300819380, "nonce":"xyz..." } Payload Base64urlデコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 110
{ "iss":"https://idp.example.com", "sub":"123456789", "aud":"abcdefg", "nonce":"xyz", "iat":1291836800, "exp":1300819380, "nonce":"xyz..." } issuer ID Tokenの発⾏社(IdP) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 111
{ "iss":"https://idp.example.com", "sub":"123456789", "aud":"abcdefg", "nonce":"xyz", "iat":1291836800, "exp":1300819380, "nonce":"xyz..." } subject ユーザー識別⼦(認証の対象者) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 112
{ "iss":"https://idp.example.com", "sub":"123456789", "aud":"abcdefg", "nonce":"xyz", "iat":1291836800, "exp":1300819380, "nonce":"xyz..." } audience Client ID(ID Tokenの発⾏先) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 113
{ "iss":"https://idp.example.com", "sub":"123456789", "aud":"abcdefg", "nonce":"xyz", "iat":1291836800, "exp":1300819380, "nonce":"xyz..." } 発⾏社(iss)が どのRP(aud)に対して どのユーザー(sub)を認証した のかを⽰している -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. リプレイ攻撃
114 ⼀般的な場合 • 有効なデータ転送が故意または不正に繰り返し/遅延される ことによる攻撃 • IPパケットの置換によるDNS偽装の⼀部のように、発信者 や攻撃者がデータを傍受し再送信することによって実⾏さ れる OpenID Connectの場合 • ID Tokenを傍受し不正ログインを試みる -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 115
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 6.Access Token/ID Token 8.Access Token 9.Claims 4.同意画⾯ 10.属性情報取得完了 7.ログイン完了 Implicit Flow リプレイ攻撃の 対象になる部分は どこでしょう︖ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 116
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 6.Access Token/ID Token 8.Access Token 9.Claims 4.同意画⾯ 10.属性情報取得完了 7.ログイン完了 Implicit Flow 通信経路から傍受されたID Tokenを 受け⼊れてしまう -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 117
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server Resource Server (UserInfo) 0.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 6.Access Token/ID Token 4.同意画⾯ Proxyなどを⽤いて ID Tokenを傍受 8.ログイン完了 7.ID Token Implicit Flow -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. nonce
(number used once) 118 • OpenID ConnectにはID Tokenのリプレイ攻撃対策と して「nonce」パラメーターが定義されている • RPがAuthorizationリクエストで指定したnonce値と 返却されるID Tokenの内部に含まれるnonce値の ⼀致を検証することで繰り返し送信されるID Tokenの リプレイ攻撃を防⽌する -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 119
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0.処理開始 1.Authorizationリクエスト(nonce=xyz) 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token 9.Claims 4.同意画⾯ 10.属性情報取得完了 7.ログイン完了 6.Access Token/ID Token(nonce=xyz) Implicit Flow RPのセッション (HTTPOnlyのCookieなど) に紐付けた値を送信 nonce値を含んだID Tokenが返却される セッションに紐づけたnonce値と⼀致するか 検証することでリプレイ攻撃を防⽌ Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 120
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0.処理開始 1.Authorizationリクエスト 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 6.Access Token/ID Token(nonce=123) 4.同意画⾯ 8.ログイン完了 7.ID Token(nonce=123) Implicit Flow 被害者のセッションに紐づくnonce値 (サービスのAuthorizationリクエストに アクセスしていなければセッションもない) と⼀致しないため置き換えを検知できる Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. nonceの検証
121 • nonceの検証はID Tokenの再送が起こりうるケースのみ必須 • クライアントサイドでID Tokenを受け取るHybridフローの場合は 必須 • Authorization CodeフローのRPとIdPのサーバー間で安全に ID Tokenが送受信される場合にはnonceの検証は任意でよい -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 5.
リプレイ攻撃対策(nonce値の検証) 122 • Tokenレスポンスで返却されるID Tokenの再送による リプレイ攻撃を対策する • RPで⽣成したnonce値をセッションCookieに紐付ける • nonce値をAuthorizationリクエストで送信する • ID Tokenに含まれるnonce値がセッションCookieに紐 付けたnonce値と同⼀であるかを検証する https://github.com/kura-lab/kuroobi-hands-on-2020/tree/20200204/practice05 演習ソースコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 123
End-User Client (User Agent) Back-End Server AuthN/AuthZ Server 0-1.処理開始 1.Authorizationリクエスト(nonce=xyz) 2.ログイン画⾯ 3.クレデンシャル 情報⼊⼒ 5.同意 8.Access Token/ID Token(nonce=xyz) 4.同意画⾯ 0-2.処理開始 6.Authorization Code 7.Tokenリクエスト(Authorization Code) 9.ログインセッション 発⾏ 10.ログイン完了 Authorization Code Flow ID Tokenに含まれる nonce値を検証 nonce値を⽣成し セッションCookieに紐づける Resource Server (UserInfo) -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 124
... // 5-1. セッションCookieに紐付けるnonce値を⽣成し保存 nonce := generateRandomString() nonceCookie := &http.Cookie{ Name: "nonce", Value: nonce, HttpOnly: true, } http.SetCookie(w, nonceCookie) log.Println("stored state and nonce in session") ... // 5-2. セッションCookieに紐づけたnonce値を指定 q.Set("nonce", nonce) ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 125
... // 5-3. ID Tokenのデータ部の分解 idTokenParts := strings.SplitN(tokenData.IDToken, ".", 3) log.Println("header: ", idTokenParts[0]) log.Println("payload: ", idTokenParts[1]) log.Println("signature: ", idTokenParts[2]) // 5-4. ID Tokenのヘッダーの検証 header, err := base64.RawURLEncoding.DecodeString(idTokenParts[0]) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode ID Token") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 126
... // 5-3. ID Tokenのデータ部の分解 idTokenParts := strings.SplitN(tokenData.IDToken, ".", 3) log.Println("header: ", idTokenParts[0]) log.Println("payload: ", idTokenParts[1]) log.Println("signature: ", idTokenParts[2]) // 5-4. ID Tokenのヘッダーの検証 header, err := base64.RawURLEncoding.DecodeString(idTokenParts[0]) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode ID Token") return } ... practice05/server.go Base64デコードではなく Base64URLデコード -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 127
... // 5-5. ID Tokenのヘッダーを格納する構造体 type IDTokenHeader struct { Type string `json:"typ"` Algorithm string `json:"alg"` KeyID string `json:"kid"` } ... // 5-6. ID Tokenのヘッダーを構造体に格納 var idTokenHeader IDTokenHeader err = json.Unmarshal(header, &idTokenHeader) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode ID Token") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 128
... // 5-7. typ値の検証 if idTokenHeader.Type != "JWT" { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "invalid id token type") return } // 5-8. alg値の検証 if idTokenHeader.Algorithm != "RS256" { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "invalid id token algorithm") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 129
... // 5-9. JWKsリクエスト jwksResponse, err := http.Get(config.OIDCURL + "/yconnect/v2/jwks") if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to get jwk") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 130
... defer func() { _, err = io.Copy(ioutil.Discard, jwksResponse.Body) if err != nil { log.Panic(err) } err = jwksResponse.Body.Close() if err != nil { log.Panic(err) } }() ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 131
{ "keys": [ { "kid": "0cc175b9c0f1b6a831c399e269772661", "kty": "RSA", "alg": "RS256", "use": "sig", "n": "0bXcnrheJ2snfq1wv6Qz8...", "e": "AQAB" }, ... ] } -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 132
... // 5-11. JWKsレスポンスを構造体に格納 var jwksData JWKsResponse err = json.NewDecoder(jwksResponse.Body).Decode(&jwksData) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to read jwk's json body") return } log.Println("requested jwks endpoint") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 133
... // 5-12. modulus値とexponent値の抽出 var modulus, exponent string for _, keySet := range jwksData.KeySets { if keySet.KeyID == idTokenHeader.KeyID { log.Println("kid: " + keySet.KeyID) if keySet.KeyType != "RSA" || keySet.Algorithm != idTokenHeader.Algorithm || keySet.Use != "sig" { w.WriteHeader(http.StatusUnauthorized) errorTemplate.Execute(w, "invalid kid, alg or use") return } modulus = keySet.Modulus exponent = keySet.Exponent break } } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 134
... if modulus == "" || exponent == "" { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to extract modulus or exponent") return } log.Println("extracted modulus and exponent") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 135
... // 5-13. n(modulus)とe(exponent)から公開鍵を⽣成 decodedModulus, err := base64.RawURLEncoding.DecodeString(modulus) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode modulus") return } decodedExponent, err := base64.StdEncoding.DecodeString(exponent) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode exponent") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 136
... var exponentBytes []byte if len(decodedExponent) < 8 { exponentBytes = make([]byte, 8-len(decodedExponent), 8) exponentBytes = append(exponentBytes, decodedExponent...) } else { exponentBytes = decodedExponent } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 137
... reader := bytes.NewReader(exponentBytes) var e uint64 err = binary.Read(reader, binary.BigEndian, &e) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to read binary exponent") return } generatedPublicKey := rsa.PublicKey{N: big.NewInt(0).SetBytes(decodedModulus), E: int(e)} log.Println("generated public key: ", generatedPubli ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 138
... // 5-14. ID Tokenの署名を検証 decodedSignature, err := base64.RawURLEncoding.DecodeString(idTokenParts[2]) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode signature") return } hash := crypto.Hash.New(crypto.SHA256) _, err = hash.Write([]byte(idTokenParts[0] + "." + idTokenParts[1])) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to create id token hash") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 139
... hashed := hash.Sum(nil) err = rsa.VerifyPKCS1v15(&generatedPublicKey, crypto.SHA256, hashed, decodedSignature) if err != nil { log.Println("failed to verify signature") w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to verify signature") return } log.Println("success to verify signature") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 140
... // 5-15. ID Tokenのペイロードをデコード decodedPayload, err := base64.RawURLEncoding.DecodeString(idTokenParts[1]) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to decode payload") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 141
... // 5-16. ID Tokenのペイロードを格納する構造体 type IDTokenPayload struct { Issuer string `json:"iss"` Subject string `json:"sub"` Audience []string `json:"aud"` Expiration int `json:"exp"` IssueAt int `json:"iat"` AuthTime int `json:"auth_time"` Nonce string `json:"nonce"` AuthenticationMethodReference []string `json:"amr"` AccessTokenHash string `json:"at_hash"` } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 142
... // 5-17. ID Tokenのペイロードを構造体へ格納 idTokenPayload := new(IDTokenPayload) err = json.Unmarshal(decodedPayload, idTokenPayload) if err != nil { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "failed to parse payload json") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 143
... // 5-18. issuer値の検証 log.Println("id token issuer: ", idTokenPayload.Issuer) if idTokenPayload.Issuer != oidcURL+"/yconnect/v2" { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched issuer") return } log.Println("success to verify issuer") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 144
... // 5-19. audience値の検証 log.Println("id token audience: ", idTokenPayload.Audience) var isValidAudience bool for _, audience := range idTokenPayload.Audience { if audience == ClientID { log.Println("mached audience: ", audience) isValidAudience = true break } } if !isValidAudience { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched audience") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 145
... // 5-20. セッションCookieからnonce値の抽出 storedNonce, err := r.Cookie("nonce") if err != nil { w.WriteHeader(http.StatusBadRequest) errorTemplate.Execute(w, "nonce cookie error") return } ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 146
... // 5-21. セッションCookieに紐づけていたnonce値の破棄 nonceCookie := &http.Cookie{ Name: "nonce", MaxAge: -1, } http.SetCookie(w, nonceCookie) log.Println("id token nonce: ", idTokenPayload.Nonce) log.Println("stored nonce: ", storedNonce.Value) if idTokenPayload.Nonce != storedNonce.Value { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "nonce does not match stored one") return } log.Println("success to verify nonce") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 147
... // 5-22. iat値の検証 log.Println("id token iat: ", idTokenPayload.IssueAt) if int(time.Now().Unix())-idTokenPayload.IssueAt >= 600 { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "too far away from current time") return } log.Println("success to verify issue at") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 148
... // 5-23. at_hash値の検証 receivedAccessTokenHash := sha256.Sum256([]byte(tokenData.AccessToken)) halfOfAccessTokenHash := receivedAccessTokenHash[:len(receivedAccessTokenHash)/2] encodedhalfOfAccessTokenHash := base64.RawURLEncoding.EncodeToString(halfOfAccessTokenHash) log.Println("id token at_hash: ", idTokenPayload.AccessTokenHash) log.Println("generated at_hash: ", encodedhalfOfAccessTokenHash) if idTokenPayload.AccessTokenHash != encodedhalfOfAccessTokenHash { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched at_hash") return } log.Println("success to verify at_hash") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 149
... // 5-23. at_hash値の検証 receivedAccessTokenHash := sha256.Sum256([]byte(tokenData.AccessToken)) halfOfAccessTokenHash := receivedAccessTokenHash[:len(receivedAccessTokenHash)/2] encodedhalfOfAccessTokenHash := base64.RawURLEncoding.EncodeToString(halfOfAccessTokenHash) log.Println("id token at_hash: ", idTokenPayload.AccessTokenHash) log.Println("generated at_hash: ", encodedhalfOfAccessTokenHash) if idTokenPayload.AccessTokenHash != encodedhalfOfAccessTokenHash { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched at_hash") return } log.Println("success to verify at_hash") ... practice05/server.go Access Tokenのハッシュ値の オクテットの前半をBase64URLエンコードした値 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 150
... // 5-24. 以下の値の検証および利⽤は任意 // - idTokenPayload.Expiration // - idTokenPayload.AuthTime // - idTokenPayload.AuthenticationMethodReference ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 151
... // 5-25. sub値の検証 log.Println("id token sub: ", idTokenPayload.Subject) log.Println("userinfo sub: ", userInfoData.Subject) if idTokenPayload.Subject != userInfoData.Subject { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched user id") return } log.Println("success to verify user id") ... practice05/server.go -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. 152
... // 5-25. sub値の検証 log.Println("id token sub: ", idTokenPayload.Subject) log.Println("userinfo sub: ", userInfoData.Subject) if idTokenPayload.Subject != userInfoData.Subject { w.WriteHeader(http.StatusInternalServerError) errorTemplate.Execute(w, "mismatched user id") return } log.Println("success to verify user id") ... practice05/server.go ID Tokenで認証したユーザーと UserInfoで取得したユーザーが⼀致しているかを検証 -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. まとめ -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. まとめ
154 1. OpenID Connectの概要説明 • OpenID ConnectはOpenIDとは異なりOAuth 2.0をベースとした認証・認 可のプロトコルである • 多様なユースケースに適応できるよう3つのフローが定義されている 2. Go⾔語によるYahoo! ID連携の実装 • Authorizationリクエスト、Tokenリクエスト、UserInfoリクエストをGo ⾔語で実装し⼀連のAuthorization Code Flowを学んだ 3. 実装のポイント、セキュリティ対策の解説 • OpenID ConnectによるCSRF対策、リプレイ攻撃対策を学んだ • RPのユーザー認証に⽤いるID Tokenの検証を実装した -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. ご清聴ありがとうございました -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. Copyright
(C) 2020 Yahoo Japan Corporation. All Rights Reserved. Appendix -
Copyright (C) 2020 Yahoo Japan Corporation. All Rights Reserved. OpenID
Connectの仕様について 158 • OpenID Connectの概要や詳細な仕様について学習をする際には、 以下のWebサイトや仕様書、解説のスライドをご参照ください。 • Welcome to OpenID Connect -openid.net- • https://openid.net/connect/ • OpenID Connect Core 1.0 incorporating errata set 1 • https://openid.net/specs/openid-connect-core-1_0.html • OpenID Connect Core 1.0 incorporating errata set 1(⽇本語訳) • https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html • OpenID Connect ⼊⾨ 〜コンシューマーにおけるID連携のトレンド〜 • https://www.slideshare.net/kura_lab/openid-connect-id