20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ディップAdvent Calendar 2018

Day 6

golang oauth2.0 サーバー側の処理

Last updated at Posted at 2018-12-06

この記事は実装の一部となります

図:

image.png

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の情報など、実装が必要)

image.png
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/

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?