diff --git a/Makefile b/Makefile index 4317f93..df86648 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ confirm: ## run/api: run the cmd/api application .PHONY: run/api run/api: - go run ./cmd/api + go run ./cmd/api -port=4002 # ==================================================================================== # # QUALITY CONTROL diff --git a/cmd/api/auth.go b/cmd/api/auth.go new file mode 100644 index 0000000..8b60ee1 --- /dev/null +++ b/cmd/api/auth.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "net/http" + "time" + + "golang.org/x/oauth2" +) + +const ( + h5Host = "http://localhost:5173" + clientID = "client_id" + clientSecret = "client_secret" + clientHost = "http://localhost:4002" + clientState = "client_state" + authHost = "http://localhost:4001" + code_challenge = "code_challenge" +) + +var ( + oauth2Config = oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + Scopes: []string{"all"}, + RedirectURL: clientHost + "/v1/oauth2", + Endpoint: oauth2.Endpoint{ + AuthURL: authHost + "/v1/oauth2/authorize", + TokenURL: authHost + "/v1/oauth2/token", + }, + } +) + +// 登录 重定向到认证服务器 +func (app *application) loginHandler(w http.ResponseWriter, r *http.Request) { + u := oauth2Config.AuthCodeURL(clientState, oauth2.SetAuthURLParam("code_challenge", genCodeChallengeS256(code_challenge)), oauth2.SetAuthURLParam("code_challenge_method", "S256")) + http.Redirect(w, r, u, http.StatusFound) +} + +// 授权回调 +func (app *application) oauth2Handler(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + state := r.Form.Get("state") + if state != clientState { + app.serverErrorResponse(w, r, errors.New("state is not valid")) + return + } + code := r.Form.Get("code") + if code == "" { + app.serverErrorResponse(w, r, errors.New("code is not found")) + return + } + token, err := oauth2Config.Exchange(r.Context(), code, oauth2.SetAuthURLParam("code_verifier", code_challenge)) + if err != nil { + app.serverErrorResponse(w, r, err) + return + } + // TODO 检查资源服务器上是否有该用户信息, 没有则通过authHost/v1/oauth2/get-user-info获取用户信息并保存到资源服务器 + http.Redirect(w, r, fmt.Sprintf("%s/authorize?access_token=%s&refresh_token=%s&expiry=%d", h5Host, token.AccessToken, token.RefreshToken, token.Expiry.Unix()), http.StatusFound) +} + +// 刷新token +// POST /v1/refresh-token +func (app *application) refreshTokenHandler(w http.ResponseWriter, r *http.Request) { + var input struct { + RefreshToken string `json:"refresh_token"` + } + err := app.readJSON(w, r, &input) + if err != nil { + app.serverErrorResponse(w, r, err) + return + } + token, err := oauth2Config.TokenSource(context.Background(), &oauth2.Token{ + RefreshToken: input.RefreshToken, + Expiry: time.Now(), + }).Token() + if err != nil { + app.serverErrorResponse(w, r, err) + return + } + err = app.writeJSON(w, http.StatusOK, envelope{"token": token}, nil) + if err != nil { + app.serverErrorResponse(w, r, err) + return + } +} + +func genCodeChallengeS256(s string) string { + s256 := sha256.Sum256([]byte(s)) + return base64.URLEncoding.EncodeToString(s256[:]) +} diff --git a/cmd/api/routes.go b/cmd/api/routes.go index 60fc46f..3d07cdb 100644 --- a/cmd/api/routes.go +++ b/cmd/api/routes.go @@ -13,6 +13,10 @@ func (app *application) routes() http.Handler { router.NotFound = http.HandlerFunc(app.notFoundResponse) router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowResponse) router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler) + // 授权 + router.HandlerFunc(http.MethodGet, "/v1/login", app.loginHandler) + router.HandlerFunc(http.MethodGet, "/v1/oauth2", app.oauth2Handler) + router.HandlerFunc(http.MethodPost, "/v1/refresh-token", app.refreshTokenHandler) router.Handler(http.MethodGet, "/debug/vars", expvar.Handler()) return app.metrics( app.recoverPanic( diff --git a/go.mod b/go.mod index 990763a..3937588 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,6 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.2 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce + golang.org/x/oauth2 v0.24.0 golang.org/x/time v0.5.0 ) diff --git a/go.sum b/go.sum index 5a15a82..d44a278 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,14 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=