Slide 1

Slide 1 text

OAuth 2.0 αʔόΛ GoݴޠͰ࣮૷ɾςετΛॻ͘ 2018/9/28 (Fri) golang.tokyo #18 @Khigashiguchi

Slide 2

Slide 2 text

About this talk background • ಛఆΫϥΠΞϯτʹର͢ΔೝূɾೝՄ • ͱ͋ΔAPIαʔόʹOAuth 2.0ͰͷೝՄΛ ड͚࣋ͭRequest HandlerΛ࣮૷͢Δ • ೝՄαʔόͱϦιʔεαʔόͷಉډ • OAuth2.0ܥϥΠϒϥϦΛ༻͍࣮ͯ૷͠ɺ ςετΛॻ͘

Slide 3

Slide 3 text

ࣗݾ঺հ • ౦ޱ ࿨ᏻ @Khigashiguchi • Server Side EngineerʢGo / PHPʣ • BASE, Inc / BASE Product Division • Blog: http:// khigashigashi.hatenablog.com/

Slide 4

Slide 4 text

About OAuth 2.0 • The OAuth 2.0 Authorization Framework • ݖݶͷೝՄʢAuthorizationʣΛߦ͏ͨΊ ͷΦʔϓϯελϯμʔυ • RFC6749 • https://tools.ietf.org/html/rfc6749 • RFC6750 • https://tools.ietf.org/html/rfc6750

Slide 5

Slide 5 text

Why OAuth 2.0 • ΫϥΠΞϯτ-αʔόؒೝূɾೝՄ • ϢʔβʔΛϑϩʔʹհ͞ͳ͍ • ೝূͨ͠ΫϥΠΞϯτʹରͯ͠ϦιʔεΞ ΫηεͷೝՄΛߦ͍͍ͨ • ελϯμʔυʹଇͬͨೝূͱ͔ͨͬͨ͠

Slide 6

Slide 6 text

OAuth 2.0 Authorization Grant Type • 4ͭͷೝՄλΠϓ͕RFC6749#4ʹͯఆٛ͞ Ε͍ͯΔ • Authorization Code Grant (#4.1) • Implicit Grant (#4.2) • Resource Owner Password Credentials Grant (#4.3) • Client Credentials Grant (#4.4)

Slide 7

Slide 7 text

Grant Type: Authorization Code Grant https://qiita.com/awakia/items/66975de18ba25f18a961

Slide 8

Slide 8 text

Which type I choose?: background again • ΫϥΠΞϯτ - αʔόؒͷೝূɾೝՄ • ϢʔβʔͷೝՄΛඞཁͱ͠ͳ͍ • ෆಛఆଟ਺Ͱ͸ͳ͘ɺಛఆΫϥΠΞϯτʹ ର͢ΔೝূɾೝՄ

Slide 9

Slide 9 text

Grant Type: Client Credentials • “The client can request an access token using only its client credentials” https://qiita.com/awakia/items/66975de18ba25f18a961 https://tools.ietf.org/html/rfc6749#section-4.4

Slide 10

Slide 10 text

About Client Credential Grant • τʔΫϯϦΫΤετΛ౤͛ɺԠ౴ͱͯ͠Ξ ΫηετʔΫϯΛड͚औΔϑϩʔ • Ϣʔβʔͷೝূ͸͓͜ͳΘΕͣɺΫϥΠΞ ϯτΞϓϦέʔγϣϯͷೝূͷΈ • ओͳϢʔεέʔεɿʮ৴པʯͰ͖ΔΫϥΠ Ξϯτʹରͯ͠ͷೝՄ

Slide 11

Slide 11 text

OAuth 2.0 Authorization Grant Type • Authorization Code Grant (#4.1) • Implicit Grant (#4.2) • Resource Owner Password Credentials Grant (#4.3) • Client Credentials Grant (#4.4) Client Credentials GrantͰ࣮૷͍ͯ͘͠

Slide 12

Slide 12 text

Way to implement OAuth Server • Use SaaS • OAuth 2.0ܥͷSaaSΛར༻͢Δ • Create Auth Server • OAuth ServerΛผ్࡞੒͢Δ • Implement Monolithic • Request Handlerͱ࣮ͯ͠૷

Slide 13

Slide 13 text

Way to implement OAuth Server Outline Merit Demerit Use SaaS OAuth 2.0ܥ ͷSaaSΛར༻ ɾ։ൃ޻਺୹ॖ ɾϓϩόΠμʔʹ ΑΔηΩϡΞ ɾར༻ྉۚ Create Auth Server OAuth Server Λ࡞੒͢Δ ɾϦιʔεαʔό ͱͷૄ݁߹ ɾશମߏ੒ͷ ෳࡶ౓ Implement Monolithic Request Handlerͱ͠ ࣮ͯ૷ ɾશମߏ੒ͷ ؆ૉԽ ɾີ݁߹

Slide 14

Slide 14 text

Way to implement OAuth Server Outline Merit Demerit Use SaaS OAuth 2.0ܥ ͷSaaSΛར༻ ɾ։ൃ޻਺୹ॖ ɾϓϩόΠμʔʹ ΑΔηΩϡΞ ɾར༻ྉۚ Create Auth Server OAuth Server Λ࡞੒͢Δ ɾϦιʔεαʔό ͱͷૄ݁߹ ɾશମߏ੒ͷ ෳࡶ౓ Implement Monolithic Request Handlerͱ͠ ࣮ͯ૷ ɾશମߏ੒ͷ ؆ૉԽ ɾີ݁߹ APIن໛ΛؑΈͯɺMonolithicʹ࣮૷͢Δ

Slide 15

Slide 15 text

Packages to implement auth handler • openshift/osin • OSIN is an OAuth2 server library for the Go language • https://github.com/openshift/osin • Star: 1477 (2018/9/28) • ory/fosite • Extensible security first OAuth 2.0 and OpenID Connect SDK for Go. • https://github.com/ory/fosite • Star: 1108 (2018/9/28)

Slide 16

Slide 16 text

Packages to implement auth handler • openshift/osin • OSIN is an OAuth2 server library for the Go language • https://github.com/openshift/osin • Star: 1477 (2018/9/28) • ory/fosite • Extensible security first OAuth 2.0 and OpenID Connect SDK for Go. • https://github.com/ory/fosite • Star: 1108 (2018/9/28) ࠓճ͸ͪ͜ΒͰ࣮૷ΛਐΊ·͢

Slide 17

Slide 17 text

෮श: Grant Type: Client Credentials • Token endpointΛ࣮૷͢Δ https://qiita.com/awakia/items/66975de18ba25f18a961 https://tools.ietf.org/html/rfc6749#section-4.4

Slide 18

Slide 18 text

Hello openshift/osin // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go

Slide 19

Slide 19 text

Hello openshift/osin // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go http handlerΛ࣮૷͢Δ

Slide 20

Slide 20 text

Hello openshift/osin // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go OAuth server༻ͷinstanceੜ੒ ͦͷࡍɺConfigurationɾStorageΛ౉͢

Slide 21

Slide 21 text

Hello openshift/osin // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go RequestΛॲཧ

Slide 22

Slide 22 text

Hello openshift/osin // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go ResponseΛॻ͖ࠐΉ

Slide 23

Slide 23 text

Hello openshift/osin > % curl -X POST -u 1234:abcd \ http://localhost:8080/oauth2/token?grant_type=client_credentials {“access_token":"4eFkLh4IRzG_73lklQlM-A","expires_in": 3600,"token_type":"Bearer"} Console

Slide 24

Slide 24 text

Todo list • ࣮ࡍʹಈ͔͍ͯ͘͠ʹ͸͍͔ͭ͘ToDo͕ ͋Δ

Slide 25

Slide 25 text

Todo list • ConfigurationʢOAuth serverͷઃఆʣ • StorageʢDatabase storageʣ • Logger • Refactoring Handler • Testing Handler

Slide 26

Slide 26 text

Configuration &ServerConfig{ AuthorizationExpiration: 250, AccessExpiration: 3600, TokenType: "Bearer", AllowedAuthorizeTypes: AllowedAuthorizeType{CODE}, AllowedAccessTypes: AllowedAccessType{AUTHORIZATION_CODE}, ErrorStatusCode: 200, AllowClientSecretInParams: false, AllowGetAccessRequest: false, RetainTokenAfterRefresh: false, } osin/config.go • OAuth Serverͱͯ͠ͷઃఆΛ͢ΔɻඞཁʹԠ ͯ͡ઃఆΛมߋ͢Δɻ

Slide 27

Slide 27 text

Storage // Storage interface type Storage interface { Clone() Storage Close() GetClient(id string) (Client, error) SaveAuthorize(*AuthorizeData) error LoadAuthorize(code string) (*AuthorizeData, error) RemoveAuthorize(code string) error SaveAccess(*AccessData) error LoadAccess(token string) (*AccessData, error) RemoveAccess(token string) error LoadRefresh(token string) (*AccessData, error) RemoveRefresh(token string) error } osin/storage.go • osin.Storage interfaceΛຬͨ͢Α͏ʹɺσʔλͷ CRUDͳͲΛߦ͏StorageΛ࣮૷͢Δɻ

Slide 28

Slide 28 text

Storage // Storage interface type Storage interface { Clone() Storage Close() GetClient(id string) (Client, error) SaveAuthorize(*AuthorizeData) error LoadAuthorize(code string) (*AuthorizeData, error) RemoveAuthorize(code string) error SaveAccess(*AccessData) error LoadAccess(token string) (*AccessData, error) RemoveAccess(token string) error LoadRefresh(token string) (*AccessData, error) RemoveRefresh(token string) error } osin/storage.go • ೝՄλΠϓʹΑͬͯෆඞཁͳ΋ͷ΋ग़ͯ͘Δɺ ͦͷ৔߹͸ۭ࣮૷ɻ Client Credentials Ͱ͸ɺ ԫ৭࿮ͷΈͰे෼

Slide 29

Slide 29 text

Storage • όοΫΤϯυ͝ͱͷαϯϓϧ࣮૷Λࢀߟʹ͢ Δɻ https://github.com/openshift/osin

Slide 30

Slide 30 text

Logger • ΞϓϦέʔγϣϯશମͷ੔߹ੑతʹ΋ɺϥ ΠϒϥϦ಺ͷLoggerΛࠩ͠ସ͍͑ͨɻ • ex: ʮ౷ҰతʹjsonܗࣜͰstdoutʹు͖ ग़͢ʯ

Slide 31

Slide 31 text

Logger // TokenHandler handle request to get access token. func () TokenHandler(w http.ResponseWriter, r *http.Request) { server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage()) resp := server.NewResponse() defer resp.Close() if ar := server.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true server.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go osin.NewServer()Ͱฦͬͯ͘Δɺ ߏ଄ମͷϑΟʔϧυʹΞΫηε͢Δ͜ͱͰɺ loggerΛࠩ͠ସ͑Δ͜ͱ͕Ͱ͖Δɻ

Slide 32

Slide 32 text

Logger // Server is an OAuth2 implementation type Server struct { Config *ServerConfig Storage Storage AuthorizeTokenGen AuthorizeTokenGen AccessTokenGen AccessTokenGen Now func() time.Time Logger Logger } osin/server.go • osin.NewServer()Ͱ͸ҎԼͷ*osin.Server struct͕ฦΓ஋

Slide 33

Slide 33 text

Logger // Server is an OAuth2 implementation type Server struct { Config *ServerConfig Storage Storage AuthorizeTokenGen AuthorizeTokenGen AccessTokenGen AccessTokenGen Now func() time.Time Logger Logger } osin/server.go • Logger fieldΛࠩ͠ସ͑Δɺosin.Logger interface Λຬ࣮ͨ͢૷Λ͢Δ

Slide 34

Slide 34 text

Logger type Logger interface { Printf(format string, v ...interface{}) } osin/log.go • ࠩ͠ସ͑ΔLogger interface͸ɺPrintf()Λ࣋ͭ ࣮૷Λ͢Δ͚ͩɻ

Slide 35

Slide 35 text

Logger // Writer specifies output of logger. var Writer io.Writer = os.Stdout type OauthLogger struct { } func (*OauthLogger) Printf(format string, v ...interface{}) { fmt.Fprintf(Writer, fmt.Sprintf(“OauthServerError: "+format, v...)) } logger.go • Logger interfaceΛຬ࣮ͨͨ͠૷

Slide 36

Slide 36 text

Logger Testing func TestServerLogger_Printf(t *testing.T) { r, w, err := os.Pipe() if err != nil { t.Errorf("unexpected by os.Pipe(): %#v", err) } logger.Writer = w l := logger.OauthLogger{} l.Printf("%s %s %s", "test", "test1", "test2") w.Close() actual, err := ioutil.ReadAll(r) if err != nil { t.Errorf("unexpected by ioutil.ReadAll(): %#v", err) } expected := "OauthServerError: test test1 test2" if diff := cmp.Diff(expected, string(actual)); diff != "" { t.Errorf("differs: (-want +got)\n%s", diff) } } logger_test.go

Slide 37

Slide 37 text

Logger Testing func TestServerLogger_Printf(t *testing.T) { r, w, err := os.Pipe() if err != nil { t.Errorf("unexpected by os.Pipe(): %#v", err) } logger.Writer = w l := logger.OauthLogger{} l.Printf("%s %s %s", "test", "test1", "test2") w.Close() actual, err := ioutil.ReadAll(r) if err != nil { t.Errorf("unexpected by ioutil.ReadAll(): %#v", err) } expected := "OauthServerError: test test1 test2" if diff := cmp.Diff(expected, string(actual)); diff != "" { t.Errorf("differs: (-want +got)\n%s", diff) } } logger_test.go os.Pipe()Ͱ༻ҙͨ͠*FileΛɺ logger.Writerʹ୅ೖ͢Δ͜ͱͰɺ logग़ྗΛςετ͢Δ

Slide 38

Slide 38 text

Refactoring Handler • ConfigurationɾStorageɾLoggerΛ༻͍ ͯHandlerΛ׬੒ͤ͞Δ • ςετ͢ΔͨΊʹɺϞοΫࠩ͠ସ͑Մೳͳ ࣮૷ʹ͢Δ

Slide 39

Slide 39 text

Refactoring Handler type Oauth2Controller struct { OauthServer *osin.Server } // NewOauth2Controller returns Oauth2.0 handler instance. func NewOauth2Controller(db repository.DB) Oauth2Controller { // Oauth server instanceੜ੒ͷͨΊʹඞཁͳ΋ͷΛ४උ storage := storage.NewOauthServerStorage(db) config := config.NewOauthServerConfig() logging := logger.OauthLogger{} server := osin.NewServer(config, storage) server.Logger = &logging return Oauth2Controller{ OauthServer: server, } } handler.go

Slide 40

Slide 40 text

Refactoring Handler type Oauth2Controller struct { OauthServer *osin.Server } // NewOauth2Controller returns Oauth2.0 handler instance. func NewOauth2Controller(db repository.DB) Oauth2Controller { // Oauth server instanceੜ੒ͷͨΊʹඞཁͳ΋ͷΛ४උ storage := storage.NewOauthServerStorage(db) config := config.NewOauthServerConfig() logging := logger.OauthLogger{} server := osin.NewServer(config, storage) server.Logger = &logging return Oauth2Controller{ OauthServer: server, } } handler.go *osin.Server Λfieldʹ࣋ͭɺControllerߏ଄ମΛ࡞Δ

Slide 41

Slide 41 text

Refactoring Handler type Oauth2Controller struct { OauthServer *osin.Server } func NewOauth2Controller(db repository.DB) Oauth2Controller { config := config.NewOauthServerConfig() storage := storage.NewOauthServerStorage(db) logging := logger.OauthLogger{} server := osin.NewServer(config, storage) server.Logger = &logging return Oauth2Controller{ OauthServer: server, } } handler.go Config, Storage, LoggerΛೖΕͨ*osin.ServerΛ࡞Δ

Slide 42

Slide 42 text

Refactoring Handler type Oauth2Controller struct { OauthServer *osin.Server } // TokenHandler handle request to get access token. func (c *Oauth2Controller) TokenHandler(w http.ResponseWriter, r *http.Request) { resp := c.OauthServer.NewResponse() defer resp.Close() if ar := c.OauthServer.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true c.OauthServer.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go

Slide 43

Slide 43 text

Refactoring Handler type Oauth2Controller struct { OauthServer *osin.Server } // TokenHandler handle request to get access token. func (c *Oauth2Controller) TokenHandler(w http.ResponseWriter, r *http.Request) { resp := c.OauthServer.NewResponse() defer resp.Close() if ar := c.OauthServer.HandleAccessRequest(resp, r); ar != nil { ar.Authorized = true c.OauthServer.FinishAccessRequest(resp, r, ar) } if err := osin.OutputJSON(resp, w, r); err != nil { RespondErrorJSON(w, res) return } return } handler.go ߏ଄ମͷϑΟʔϧυʹ࣋ͬͨOauthServerΛར༻ͯ͠ɺ ϥΠϒϥϦͷػೳΛݺͼग़͢ɻ

Slide 44

Slide 44 text

Complete OAuth Token Handler > % curl -X POST -u “client_id”:”client_secret” \ http://localhost:8080/oauth2/token?grant_type=client_credentials {“access_token”:"4eFkLh4IRzG_73lklQlM-A","expires_in": 3600,"token_type":"Bearer"} Console

Slide 45

Slide 45 text

Testing Handler • ςετΛ͢ΔͨΊʹmockʹࠩ͠ସ͑Δඞ ཁ͕͋Δ΋ͷ • Storage • ࣮databaseΞΫηε • AccessTokenGenerator • ࣮ߦͷͨͼʹҟͳΔaccess_tokenੜ੒

Slide 46

Slide 46 text

Testing Handler: mock Storage // Storage interface type Storage interface { Clone() Storage Close() GetClient(id string) (Client, error) SaveAuthorize(*AuthorizeData) error LoadAuthorize(code string) (*AuthorizeData, error) RemoveAuthorize(code string) error SaveAccess(*AccessData) error LoadAccess(token string) (*AccessData, error) RemoveAccess(token string) error LoadRefresh(token string) (*AccessData, error) RemoveRefresh(token string) error } osin/storage.go • ෮शɿosin.Storage interface

Slide 47

Slide 47 text

Testing Handler: mock Storage // mockStorage implements osin.Storage interface. type mockStorage struct { getClientResult osin.Client getClientErr error saveAccessErr error } func (m *mockStorage) GetClient(id string) (osin.Client, error) { return m.getClientResult, m.getClientErr } func (m *mockStorage) SaveAccess(*osin.AccessData) error { return m.saveAccessErr } // ͦͷଞɺඞཁͳϝιουΛ࣋ͭ handler_test.go

Slide 48

Slide 48 text

Testing Handler: mock Storage // mockStorage implements osin.Storage interface. type mockStorage struct { getClientResult osin.Client getClientErr error saveAccessErr error } func (m *mockStorage) GetClient(id string) (osin.Client, error) { return m.getClientResult, m.getClientErr } func (m *mockStorage) SaveAccess(*osin.AccessData) error { return m.saveAccessErr } // ͦͷଞɺඞཁͳϝιουΛ࣋ͭ handler_test.go ಈతʹϞοΫͷ໭Γ஋͕ม͑ΕΔΑ͏ʹɺ ςʔϒϧۦಈςετʹͯέʔε͝ͱʹ஋͕ม͑ΒΕΔΑ͏ʹ͢Δɻ

Slide 49

Slide 49 text

Testing Handler: mock AccessTokenGen // Server is an OAuth2 implementation type Server struct { Config *ServerConfig Storage Storage AuthorizeTokenGen AuthorizeTokenGen AccessTokenGen AccessTokenGen Now func() time.Time Logger Logger } osin/server.go • ෮शɿosin.Server struct

Slide 50

Slide 50 text

Testing Handler: mocking Storage // Server is an OAuth2 implementation type Server struct { Config *ServerConfig Storage Storage AuthorizeTokenGen AuthorizeTokenGen AccessTokenGen AccessTokenGen Now func() time.Time Logger Logger } osin/server.go • AccessTokenGen fieldΛࠩ͠ସ͑Δ͜ͱͰϞοΫԽ͢Δ

Slide 51

Slide 51 text

Testing Handler: mocking Storage // AccessTokenGen generates access tokens type AccessTokenGen interface { GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) } osin/access.go • AccessTokenGen interfaceΛຬͨ͢ϞοΫ࣮૷Λߦ͏

Slide 52

Slide 52 text

Testing Handler // prepare httptest w := httptest.NewRecorder() r = httptest.NewRequest("POST", "/oauth2/token?grant_type=client_credentials", nil) r.SetBasicAuth(“client_id", “client_secret”) // create mock oauth server ms := mockStorage{ getClientResult: &osin.DefaultClient{ Id: clientID, Secret: clientSecret, RedirectUri: “http://example.com“, UserData: 1, }, getClientErr: nil, saveAccessErr: nil, } cf := config.NewOauthServerConfig() tg := mockTokenGen{ mockToken: “test-token”, } oServer := osin.NewServer(cf, &ms) oServer.AccessTokenGen = &tg // execute handler c := controller.Oauth2Controller{ OauthServer: oServer, } c.TokenHandler(w, r) res := w.Result() defer res.Body.Close() //ɹҎԼɺΞαʔγϣϯ handler_test.go

Slide 53

Slide 53 text

Testing Handler // prepare httptest w := httptest.NewRecorder() r = httptest.NewRequest("POST", "/oauth2/token?grant_type=client_credentials", nil) r.SetBasicAuth(“client_id", “client_secret”) // create mock oauth server ms := mockStorage{ getClientResult: &osin.DefaultClient{ Id: clientID, Secret: clientSecret, RedirectUri: “http://example.com“, UserData: 1, }, getClientErr: nil, saveAccessErr: nil, } cf := config.NewOauthServerConfig() tg := mockTokenGen{ mockToken: “test-token”, } oServer := osin.NewServer(cf, &ms) oServer.AccessTokenGen = &tg // execute handler c := controller.Oauth2Controller{ OauthServer: oServer, } c.TokenHandler(w, r) res := w.Result() defer res.Body.Close() //ɹҎԼɺΞαʔγϣϯ handler_test.go ࣮ߦલʹϞοΫʹࠩ͠ସ͍͑ͯ͘ɺҎޙͷεϥΠυͰৄࡉɻ

Slide 54

Slide 54 text

Testing Handler // create mock oauth server ms := mockStorage{ getClientResult: &osin.DefaultClient{ Id: clientID, Secret: clientSecret, RedirectUri: “http://example.com“, UserData: 1, }, getClientErr: nil, saveAccessErr: nil, } cf := config.NewOauthServerConfig() tg := mockTokenGen{ mockToken: “test-token”, } oServer := osin.NewServer(cf, &ms) oServer.AccessTokenGen = &tg // execute handler c := controller.Oauth2Controller{ OauthServer: oServer, } handler_test.go StorageͷmockΛ࡞੒

Slide 55

Slide 55 text

Testing Handler // create mock oauth server ms := mockStorage{ getClientResult: &osin.DefaultClient{ Id: clientID, Secret: clientSecret, RedirectUri: “http://example.com“, UserData: 1, }, getClientErr: nil, saveAccessErr: nil, } cf := config.NewOauthServerConfig() tg := mockTokenGen{ mockToken: “test-token”, } oServer := osin.NewServer(cf, &ms) oServer.AccessTokenGen = &tg // execute handler c := controller.Oauth2Controller{ OauthServer: oServer, } handler_test.go access_tokenੜ੒ΛϞοΫʹࠩ͠ସ͑ɺ”test-token”͕ҰҙʹฦΔ Α͏ʹɻ

Slide 56

Slide 56 text

Testing Handler // ҎલɺલεϥΠυ಺༰ c.TokenHandler(w, r) res := w.Result() defer res.Body.Close() if expected := http.StatusOK; expected != res.StatusCode { t.Errorf("expected status code: %#v, given code: %#v", expected, res.StatusCode) } if expected := "application/json; charset=utf-8"; expected != res.Header.Get("Content- Type") { t.Errorf("unexpected response Content-Type, expected: %#v, but given #%v", expected, res.Header.Get("Content-Type")) } expected := resFormat{ AccessToken: token, ExpiresIn: 3600, TokenType: "Bearer", } actual := resFormat{} if err := json.NewDecoder(res.Body).Decode(&actual); err != nil { t.Errorf("unexpected error occured: %#v, given response: %#v", err, res) } if diff := cmp.Diff(expected, actual); diff != "" { t.Errorf("differs: (-want +got)\n%s", diff) } handler_test.go github.com/google/go-cmp/cmp Ͱͷɺ Ξαʔγϣϯ

Slide 57

Slide 57 text

Todo list • ✔ ConfigurationʢOAuth serverͷઃఆʣ • ✔ StorageʢDatabase storageʣ • ✔ Logger • ✔ Refactoring Handler • ✔ Testing Handler

Slide 58

Slide 58 text

·ͱΊ • Client credentials GrantΛ࣮ݱ͢ΔͨΊͷ Access Token EndpointΛ࣮૷ͨ͠ɻ • OAuth 2.0 ͷೝՄΛߦ͏ϋϯυϥʔͷςετՄ ೳͳ࣮૷Λɺopenshift/osinͰߦͬͨ • ϥΠϒϥϦ͕ఏڙ͍ͯ͠ΔinterfaceΛ࣮૷ͨ͠ mockΛ࡞ΓɺςετΛͨ͠

Slide 59

Slide 59 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·͠ ͨɻ @Khigashiguchi