この記事は実装の一部となります
図:
pkg:
import (
"github.com/openshift/osin"
)
構造体:
// App ...
type App struct {
OAuthServer *osin.Server
DBClient db.DB
}
DB周り (ID/Password認証情報など、実装が必要)
// DB ...
type DB interface {
Close()
CheckIDPassword(ID,Password) error
CheckIDByFefresToken(refreshToken string) error
//...
}
// ...
// 各メソッドの実装
Storage周り (accesstoken/refreshtokenの情報など、実装が必要)
https://github.com/openshift/osin#storage-backends
osin/storage.go
// Storage interface
type Storage interface {
// Clone the storage if needed. For example, using mgo, you can clone the session with session.Clone
// to avoid concurrent access problems.
// This is to avoid cloning the connection at each method access.
// Can return itself if not a problem.
Clone() Storage
// Close the resources the Storage potentially holds (using Clone for example)
Close()
// GetClient loads the client by id (client_id)
GetClient(id string) (Client, error)
// SaveAuthorize saves authorize data.
SaveAuthorize(*AuthorizeData) error
// LoadAuthorize looks up AuthorizeData by a code.
// Client information MUST be loaded together.
// Optionally can return error if expired.
LoadAuthorize(code string) (*AuthorizeData, error)
// RemoveAuthorize revokes or deletes the authorization code.
RemoveAuthorize(code string) error
// SaveAccess writes AccessData.
// If RefreshToken is not blank, it must save in a way that can be loaded using LoadRefresh.
SaveAccess(*AccessData) error
// LoadAccess retrieves access data by token. Client information MUST be loaded together.
// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
// Optionally can return error if expired.
LoadAccess(token string) (*AccessData, error)
// RemoveAccess revokes or deletes an AccessData.
RemoveAccess(token string) error
// LoadRefresh retrieves refresh AccessData. Client information MUST be loaded together.
// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
// Optionally can return error if expired.
LoadRefresh(token string) (*AccessData, error)
// RemoveRefresh revokes or deletes refresh AccessData.
RemoveRefresh(token string) error
}
authorize_code 認証エンドポイント:
// Authorize ...
func (a *App) Authorize(w http.ResponseWriter, r *http.Request) {
resp := a.OAuthServer.NewResponse()
defer resp.Close()
if ar := a.OAuthServer.HandleAuthorizeRequest(resp, r); ar != nil {
id := r.FormValue("id")
password := r.FormValue("password")
//csrftoken checkなど
//...
//DBにUserとPassword確認
err := a.DBClient.CheckIDPassword(id, password)
if err == sql.ErrNoRows {
// NoRows場合の処理
} else if err != nil {
// error処理
}
ar.Authorized = true
a.OAuthServer.FinishAuthorizeRequest(resp, r, ar)
osin.OutputJSON(resp, w, r)
}
}
実際使ってるメソッドはライブラリーのHandleAuthorizeRequestメソッドなので、中身:
HandleAuthorizeRequestの中身(一部分省略)
osin/authorize.go
// authorization requests
func (s *Server) HandleAuthorizeRequest(w *Response, r *http.Request) *AuthorizeRequest {
r.ParseForm()
// create the authorization request
unescapedUri, err := url.QueryUnescape(r.FormValue("redirect_uri"))
if err != nil {
w.SetErrorState(E_INVALID_REQUEST, "", "")
w.InternalError = err
return nil
}
// ...
ret := &AuthorizeRequest{
State: r.FormValue("state"),
Scope: r.FormValue("scope"),
//...
}
// must have a valid client
ret.Client, err = w.Storage.GetClient(r.FormValue("client_id"))
if err == ErrNotFound {
w.SetErrorState(E_UNAUTHORIZED_CLIENT, "", ret.State)
return nil
}
// ...
requestType := AuthorizeRequestType(r.FormValue("response_type"))
if s.Config.AllowedAuthorizeTypes.Exists(requestType) {
switch requestType {
case CODE:
// ...
case TOKEN:
// ...
}
return ret
}
//...
}
パラメターについて:
//response_type 必須
アクセストークンを要求する場合は "token" を設定します。認可コードの場合は "code" を設定します。
//client_id 必須
クライアントを設定します。
//redirect_uri 条件付き必須
クライアントと認可サーバ間で事前にリダイレクト先の URI が決められていない場合は必須になります。
エンドユーザによる認可が完了した際に、認可サーバがユーザーエージェントをリダイレクトする先の絶対 URI を設定します。
//scope 任意
リクエストで要求するアクセス権のスコープを、空白区切りの文字列で指定します。 scopeの値に何を指定するかは認可サーバによって定義
されます。 空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせた範囲が要求するアクセス権になります。
//state 任意
リクエストから完了時のコールバックまでの間、クライアントが状態管理できる値を指定します。
認可サーバは指定された値をそのままリダイレクトURIに、ユーザーエージェントをリダイレクトします。
Accesstoken、refreshTokenの発行、保存など
// Token ...
func (a *App) Token(w http.ResponseWriter, r *http.Request) {
resp := a.OAuthServer.NewResponse()
defer resp.Close()
if ar := a.OAuthServer.HandleAccessRequest(resp, r); ar != nil {
switch ar.Type {
case osin.AUTHORIZATION_CODE:
ar.Authorized = true
case osin.REFRESH_TOKEN:
// DBにtokenと紐ついてるuserの存在(退会の場合あり)を確認
err := a.DBClient.CheckIDByFefresToken(ar.UserData.(string))
// NoRows and error処理 ...
ar.Authorized = true
case osin.PASSWORD:
// DBにUserとPassword確認
err := a.DBClient.CheckIDPassword(ar.Username, ar.Password)
// NoRows and error処理 ...
ar.Authorized = true
}
// FinishAccessRequestが全部やってくれた
a.OAuthServer.FinishAccessRequest(resp, r, ar)
}
if resp.InternalError != nil {
// error処理 ...
}
osin.OutputJSON(resp, w, r)
}
Accesstoken、refreshTokenの発行など FinishAccessRequestがやってくれた
osin/access.go
func (s *Server) FinishAccessRequest(w *Response, r *http.Request, ar *AccessRequest) {
// don't process if is already an error
if w.IsError {
return
}
redirectUri := r.FormValue("redirect_uri")
// Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
if ar.RedirectUri != "" {
redirectUri = ar.RedirectUri
}
if ar.Authorized {
var ret *AccessData
var err error
if ar.ForceAccessData == nil {
// generate access token
ret = &AccessData{
Client: ar.Client,
AuthorizeData: ar.AuthorizeData,
AccessData: ar.AccessData,
RedirectUri: redirectUri,
CreatedAt: s.Now(),
ExpiresIn: ar.Expiration,
UserData: ar.UserData,
Scope: ar.Scope,
}
// accesstoken and refreshToken ここで発行する
ret.AccessToken, ret.RefreshToken, err = s.AccessTokenGen.GenerateAccessToken(ret, ar.GenerateRefresh)
if err != nil {
s.setErrorAndLog(w, E_SERVER_ERROR, err, "finish_access_request=%s", "error generating token")
return
}
} else {
ret = ar.ForceAccessData
}
// DBに保存までやってくれる、インタフェース:Storegeの実装が必要
if err = w.Storage.SaveAccess(ret); err != nil {
s.setErrorAndLog(w, E_SERVER_ERROR, err, "finish_access_request=%s", "error saving access token")
return
}
// ...
// output data
w.Output["access_token"] = ret.AccessToken
w.Output["token_type"] = s.Config.TokenType
w.Output["expires_in"] = ret.ExpiresIn
if ret.RefreshToken != "" {
w.Output["refresh_token"] = ret.RefreshToken
}
if ret.Scope != "" {
w.Output["scope"] = ret.Scope
}
} else {
s.setErrorAndLog(w, E_ACCESS_DENIED, nil, "finish_access_request=%s", "authorization failed")
}
}
参考:
https://murashun.jp/blog/20150920-01.html
https://www.infoscoop.org/blogjp/2014/05/12/1858/