Share
Embed
Transcript
59 slides
-
About this talk background • ಛఆΫϥΠΞϯτʹର͢ΔೝূɾೝՄ • ͱ͋ΔAPIαʔόʹOAuth 2.0ͰͷೝՄΛ ड͚࣋ͭRequest
HandlerΛ࣮͢Δ • ೝՄαʔόͱϦιʔεαʔόͷಉډ • OAuth2.0ܥϥΠϒϥϦΛ༻͍࣮ͯ͠ɺ ςετΛॻ͘ -
ࣗݾհ • ౦ޱ ᏻ @Khigashiguchi • Server Side EngineerʢGo /
PHPʣ • BASE, Inc / BASE Product Division • Blog: http:// khigashigashi.hatenablog.com/ -
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 -
Why OAuth 2.0 • ΫϥΠΞϯτ-αʔόؒೝূɾೝՄ • ϢʔβʔΛϑϩʔʹհ͞ͳ͍ • ೝূͨ͠ΫϥΠΞϯτʹରͯ͠ϦιʔεΞ ΫηεͷೝՄΛߦ͍͍ͨ
• ελϯμʔυʹଇͬͨೝূͱ͔ͨͬͨ͠ -
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) -
Which type I choose?: background again • ΫϥΠΞϯτ - αʔόؒͷೝূɾೝՄ
• ϢʔβʔͷೝՄΛඞཁͱ͠ͳ͍ • ෆಛఆଟͰͳ͘ɺಛఆΫϥΠΞϯτʹ ର͢ΔೝূɾೝՄ -
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 -
About Client Credential Grant • τʔΫϯϦΫΤετΛ͛ɺԠͱͯ͠Ξ ΫηετʔΫϯΛड͚औΔϑϩʔ • Ϣʔβʔͷೝূ͓͜ͳΘΕͣɺΫϥΠΞ ϯτΞϓϦέʔγϣϯͷೝূͷΈ
• ओͳϢʔεέʔεɿʮ৴པʯͰ͖ΔΫϥΠ Ξϯτʹରͯ͠ͷೝՄ -
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Ͱ࣮͍ͯ͘͠ -
Way to implement OAuth Server • Use SaaS • OAuth
2.0ܥͷSaaSΛར༻͢Δ • Create Auth Server • OAuth ServerΛผ్࡞͢Δ • Implement Monolithic • Request Handlerͱ࣮ͯ͠ -
Way to implement OAuth Server Outline Merit Demerit Use SaaS
OAuth 2.0ܥ ͷSaaSΛར༻ ɾ։ൃॖ ɾϓϩόΠμʔʹ ΑΔηΩϡΞ ɾར༻ྉۚ Create Auth Server OAuth Server Λ࡞͢Δ ɾϦιʔεαʔό ͱͷૄ݁߹ ɾશମߏͷ ෳࡶ Implement Monolithic Request Handlerͱ͠ ࣮ͯ ɾશମߏͷ ؆ૉԽ ɾີ݁߹ -
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ʹ࣮͢Δ -
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) -
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) ࠓճͪ͜ΒͰ࣮ΛਐΊ·͢ -
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 -
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Λ࣮͢Δ -
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Λ͢ -
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Λॲཧ -
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Λॻ͖ࠐΉ -
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 -
Todo list • ConfigurationʢOAuth serverͷઃఆʣ • StorageʢDatabase storageʣ • Logger
• Refactoring Handler • Testing Handler -
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ͱͯ͠ͷઃఆΛ͢ΔɻඞཁʹԠ ͯ͡ઃఆΛมߋ͢Δɻ -
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Λ࣮͢Δɻ -
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 Ͱɺ ԫ৭ͷΈͰे -
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Λࠩ͠ସ͑Δ͜ͱ͕Ͱ͖Δɻ -
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͕ฦΓ -
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 Λຬ࣮ͨ͢Λ͢Δ -
Logger type Logger interface { Printf(format string, v ...interface{}) }
osin/log.go • ࠩ͠ସ͑ΔLogger interfaceɺPrintf()Λ࣋ͭ ࣮Λ͢Δ͚ͩɻ -
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Λຬ࣮ͨͨ͠ -
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 -
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ग़ྗΛςετ͢Δ -
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 -
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ߏମΛ࡞Δ -
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Λ࡞Δ -
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 -
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Λར༻ͯ͠ɺ ϥΠϒϥϦͷػೳΛݺͼग़͢ɻ -
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 -
Testing Handler • ςετΛ͢ΔͨΊʹmockʹࠩ͠ସ͑Δඞ ཁ͕͋Δͷ • Storage • ࣮databaseΞΫηε •
AccessTokenGenerator • ࣮ߦͷͨͼʹҟͳΔaccess_tokenੜ -
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 -
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 -
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 ಈతʹϞοΫͷΓ͕ม͑ΕΔΑ͏ʹɺ ςʔϒϧۦಈςετʹͯέʔε͝ͱʹ͕ม͑ΒΕΔΑ͏ʹ͢Δɻ -
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 -
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Λࠩ͠ସ͑Δ͜ͱͰϞοΫԽ͢Δ -
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Λຬͨ͢ϞοΫ࣮Λߦ͏ -
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 -
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 ࣮ߦલʹϞοΫʹࠩ͠ସ͍͑ͯ͘ɺҎޙͷεϥΠυͰৄࡉɻ -
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Λ࡞ -
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”͕ҰҙʹฦΔ Α͏ʹɻ -
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 Ͱͷɺ Ξαʔγϣϯ -
Todo list • ✔ ConfigurationʢOAuth serverͷઃఆʣ • ✔ StorageʢDatabase storageʣ
• ✔ Logger • ✔ Refactoring Handler • ✔ Testing Handler -
·ͱΊ • Client credentials GrantΛ࣮ݱ͢ΔͨΊͷ Access Token EndpointΛ࣮ͨ͠ɻ • OAuth
2.0 ͷೝՄΛߦ͏ϋϯυϥʔͷςετՄ ೳͳ࣮Λɺopenshift/osinͰߦͬͨ • ϥΠϒϥϦ͕ఏڙ͍ͯ͠ΔinterfaceΛ࣮ͨ͠ mockΛ࡞ΓɺςετΛͨ͠