Jak zbudować aplikację internetową za pomocą Go, Gin i React

Ten artykuł został pierwotnie opublikowany na moim blogu

TL; DR: W tym samouczku pokażę, jak łatwo jest zbudować aplikację internetową za pomocą Go i frameworka Gin i dodać do niej uwierzytelnianie. Sprawdź repozytorium Github, aby znaleźć kod, który zamierzamy napisać.

Gin to wysokowydajna mikro-struktura. Dostarcza bardzo minimalistyczną strukturę, która zawiera tylko najważniejsze funkcje, biblioteki i funkcjonalności potrzebne do tworzenia aplikacji internetowych i mikrousług. Ułatwia budowanie potoku obsługi żądań z modułowych elementów wielokrotnego użytku. Robi to, umożliwiając pisanie oprogramowania pośredniczącego, które można podłączyć do jednego lub więcej programów obsługi żądań lub grup tych programów.

Cechy ginu

Gin to szybka, prosta, ale w pełni funkcjonalna i bardzo wydajna platforma internetowa dla Go. Zapoznaj się z niektórymi z poniższych funkcji, które sprawiają, że jest to godny model do rozważenia przy następnym projekcie Golang.

  • Szybkość: Gin został stworzony z myślą o szybkości. Framework oferuje routing oparty na drzewie Radix i niewielką ilość pamięci. Brak odbicia. Przewidywalna wydajność API.
  • Bez awarii : Gin może wychwytywać awarie lub paniki podczas działania i może po nich odzyskać. W ten sposób Twoja aplikacja będzie zawsze dostępna.
  • Routing: Gin zapewnia interfejs routingu, który pozwala wyrazić, jak powinna wyglądać Twoja aplikacja internetowa lub trasy interfejsu API.
  • Walidacja JSON: Gin może łatwo analizować i weryfikować żądania JSON, sprawdzając, czy istnieją wymagane wartości.
  • Zarządzanie błędami : Gin zapewnia wygodny sposób zbierania wszystkich błędów, które wystąpiły podczas żądania HTTP. Ostatecznie oprogramowanie pośredniczące może zapisać je w pliku dziennika lub w bazie danych i wysłać przez sieć.
  • Wbudowane renderowanie: Gin zapewnia łatwy w użyciu interfejs API do renderowania JSON, XML i HTML.

Wymagania wstępne

Aby postępować zgodnie z tym samouczkiem, musisz mieć zainstalowany Go na swoim komputerze, przeglądarkę internetową, aby wyświetlić aplikację, oraz wiersz poleceń, aby wykonywać polecenia kompilacji.

Go, lub jak zwykle nazywa się Golang , to język programowania opracowany przez Google do tworzenia nowoczesnego oprogramowania. Go to język stworzony do sprawnego i szybkiego wykonywania zadań. Kluczowe zalety Go to:

  • Silnie wpisane i zebrane śmieci
  • Niesamowicie szybkie czasy kompilacji
  • Wbudowana współbieżność
  • Obszerna biblioteka standardowa

Przejdź do sekcji pobierania w witrynie Go, aby uruchomić Go na swoim komputerze.

Tworzenie aplikacji z Ginem

Będziemy budować prostą aplikację z listą żartów z Ginem . Nasza aplikacja zawiera listę głupich dowcipów z taty. Zamierzamy dodać do niego uwierzytelnianie, aby wszyscy zalogowani użytkownicy mieli przywilej polubienia i przeglądania żartów.

Pozwoli nam to zilustrować, jak Gin może być używany do tworzenia aplikacji internetowych i / lub interfejsów API.

Będziemy korzystać z następujących funkcjonalności oferowanych przez Gin:

  • Oprogramowanie pośredniczące
  • Wytyczanie
  • Grupowanie tras

Gotowi do startu start

Napiszemy całą naszą aplikację Go w main.gopliku. Ponieważ jest to mała aplikacja, łatwo będzie ją zbudować za pomocą go runsamego terminala.

Utworzymy nowy katalog golang-ginw naszym obszarze roboczym Go, a następnie main.goplik w nim:

$ mkdir -p $GOPATH/src/github.com/user/golang-gin $ cd $GOPATH/src/github.com/user/golang-gin $ touch main.go

Zawartość main.gopliku:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Start and run the server router.Run(":3000") }

Będziemy musieli utworzyć więcej katalogów dla naszych plików statycznych. W tym samym katalogu, w którym znajduje się main.goplik, utwórzmy viewsfolder. W viewsfolderze utwórz jsfolder i index.htmlplik w nim.

index.htmlPlik będzie bardzo prosta do teraz:

   Jokeish App   

Welcome to the Jokeish App

Zanim przetestujemy to, co mamy do tej pory, zainstalujmy dodane zależności:

$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-gonic/contrib/static

Aby zobaczyć, co działa, musimy uruchomić nasz serwer, uruchamiając go run main.go.

Po uruchomieniu aplikacji przejdź do //localhost:3000w przeglądarce. Jeśli wszystko poszło dobrze, powinien zostać wyświetlony tekst nagłówka poziomu 1 Witamy w aplikacji Jokeish .

Definiowanie API

Dodajmy więcej kodu do naszego main.gopliku dla naszych definicji API. Zaktualizujemy naszą mainfunkcję dwoma trasami /jokes/i /jokes/like/:jokeIDgrupą tras /api/.

func main() { // ... leave the code above untouched... // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Treść main.gopliku powinna wyglądać następująco:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) // Start and run the server router.Run(":3000") } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Uruchommy ponownie naszą aplikację go run main.goi uzyskaj dostęp do naszych tras. //localhost:3000/api/jokeszwróci 200 OKodpowiedź nagłówka wraz z komunikatem jokes handler not implemented yet. Żądanie POST //localhost:3000/api/jokes/like/1zwracające 200 OKnagłówek i wiadomość Likejoke handler not implemented yet.

Dane dowcipów

Ponieważ mamy już zestaw definicji tras, który robi tylko jedną rzecz (zwraca odpowiedź JSON), nieco wzbogacimy naszą bazę kodu, dodając do niej więcej kodu.

// ... leave the code above untouched... // Let's create our Jokes struct. This will contain information about a Joke // Joke contains information about a single Joke type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } // We'll create a list of jokes var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } func main() { // ... leave this block untouched... } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { // confirm Joke ID sent is valid // remember to import the `strconv` package if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke, and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes += 1 } } // return a pointer to the updated jokes list c.JSON(http.StatusOK, &jokes) } else { // Joke ID is invalid c.AbortWithStatus(http.StatusNotFound) } } // NB: Replace the JokeHandler and LikeJoke functions in the previous version to the ones above

Ponieważ nasz kod wygląda dobrze, przejdźmy dalej i przetestujmy nasze API. Możemy przetestować za pomocą cURLlub postman, a następnie wysłać GETprośbę o //localhost:3000/jokespobranie pełnej listy żartów oraz POSTprośbę //localhost:3000/jokes/like/{jokeid}o zwiększenie liczby polubień żartu.

$ curl //localhost:3000/api/jokes $ curl -X POST //localhost:3000/api/jokes/like/4

Budowanie interfejsu użytkownika (React)

Mamy już nasze API, więc zbudujmy frontend, który będzie prezentował dane z naszego API. W tym celu użyjemy React. Nie będziemy zagłębiać się w React, ponieważ będzie on poza zakresem tego samouczka. Jeśli chcesz dowiedzieć się więcej o React, zajrzyj do oficjalnego samouczka. Możesz zaimplementować interfejs użytkownika z dowolnym frameworkiem frontendowym, z którym czujesz się komfortowo.

Ustawiać

Zmodyfikujemy index.htmlplik, aby dodać zewnętrzne biblioteki potrzebne do uruchomienia Reacta. Następnie musimy utworzyć app.jsxplik w views/jskatalogu, który będzie zawierał nasz kod React.

Nasz index.htmlplik powinien wyglądać następująco:

     Jokeish App 

Budowanie naszych komponentów

W Reakcie widoki są podzielone na komponenty. Będziemy musieli zbudować kilka komponentów:

  • Appkomponent jako głównego wejścia tego uruchamia aplikację
  • Homeskładnik, który zmierzy non zalogowanych
  • LoggedInskładnikiem treści widoczne tylko przez użytkowników uwierzytelnionych
  • oraz Jokekomponent do wyświetlania listy dowcipów.

Zapiszemy wszystkie te komponenty w app.jsxpliku.

Składnik aplikacji

Ten komponent ładuje całą naszą aplikację React. Decyduje, który składnik ma być wyświetlany, na podstawie tego, czy użytkownik jest uwierzytelniony, czy nie. Zaczniemy od samej podstawy, a później zaktualizujemy ją o większą funkcjonalność.

class App extends React.Component { render() { if (this.loggedIn) { return (); } else { return (); } } }

Składnik Home

Ten składnik jest wyświetlany niezalogowanym użytkownikom wraz z przyciskiem otwierającym ekran blokady hostowanej, na którym mogą się zarejestrować lub zalogować. Tę funkcjonalność dodamy później.

class Home extends React.Component { render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ) } }

Składnik LoggedIn

Ten składnik jest wyświetlany, gdy użytkownik jest uwierzytelniany. Przechowuje w swojej statetablicy żarty, które są zapełniane, gdy komponent jest montowany.

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] } } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i){ return (); })} ) } }

Komponent żart

JokeSkładnik będzie zawierać informacje o każdej pozycji z odpowiedzią na żarty być wyświetlane.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "" } this.like = this.like.bind(this); } like() { // ... we'll add this block later } render() { return ( #{this.props.joke.id} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Napisaliśmy nasze komponenty, więc powiedzmy teraz Reactowi, gdzie renderować aplikację. Dodamy poniższy blok kodu na końcu naszego app.jsxpliku.

ReactDOM.render(, document.getElementById('app'));

Zrestartujmy nasz serwer Go go run main.goi przejdźmy do adresu URL naszej aplikacji //localhost:3000/. Zobaczysz, że Homekomponent jest renderowany.

Zabezpieczanie naszej aplikacji żartów za pomocą Auth0

Auth0 wystawia tokeny internetowe JSON przy każdym logowaniu dla Twoich użytkowników. Oznacza to, że możesz mieć solidną infrastrukturę tożsamości, w tym logowanie jednokrotne, zarządzanie użytkownikami, obsługę dostawców tożsamości społecznościowych (Facebook, Github, Twitter itp.), Dostawców tożsamości korporacyjnych (Active Directory, LDAP, SAML itp.) i własną bazę danych użytkowników, zawierającą zaledwie kilka wierszy kodu.

Możemy łatwo skonfigurować uwierzytelnianie w naszej aplikacji GIN za pomocą Auth0. Będziesz potrzebować konta, aby śledzić tę część. Jeśli nie masz jeszcze konta Auth0, zarejestruj się teraz.

Zastrzeżenie: to nie jest treść sponsorowana.

Tworzenie klienta API

Nasze tokeny będą generowane za pomocą Auth0, więc musimy stworzyć API i klienta z naszego pulpitu nawigacyjnego Auth0. Ponownie, jeśli jeszcze tego nie zrobiłeś, zarejestruj konto Auth0.

Aby utworzyć nowy interfejs API, przejdź do sekcji interfejsów API na pulpicie nawigacyjnym i kliknij przycisk Utwórz interfejs API .

Wybierz nazwę API i identyfikator . Identyfikator będzie odbiorcą oprogramowania pośredniego. Algorytm Podpisanie powinno być RS256 .

Aby utworzyć nowego klienta, przejdź do sekcji klientów na pulpicie nawigacyjnym i kliknij przycisk Utwórz klienta . Wybierz typ Regular Web Applications.

Po utworzeniu klienta zwróć uwagę na client_idi client_secret, ponieważ będziemy ich później potrzebować.

Musimy dodać poświadczenia potrzebne do naszego API do zmiennej środowiskowej. W katalogu głównym utwórz nowy plik .envi dodaj do niego następujący plik , wraz ze szczegółami z pulpitu nawigacyjnego Auth0:

export export export AUTH0_DOMAIN="yourdomain.auth0.com" export

Zabezpieczanie naszych punktów końcowych API

Obecnie nasze API jest otwarte na świat. Musimy zabezpieczyć nasze punkty końcowe, aby tylko autoryzowani użytkownicy mieli do nich dostęp.

Zamierzamy skorzystać z oprogramowania pośredniczącego JWT, aby sprawdzić prawidłowy token sieciowy JSON z każdego żądania trafiającego do naszych punktów końcowych.

Stwórzmy nasze oprogramowanie pośredniczące:

// ... var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) // register our actual jwtMiddleware jwtMiddleWare = jwtMiddleware // ... the rest of the code below this function doesn't change yet } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } }

W powyższym kodzie mamy nową jwtMiddleWarezmienną, która jest inicjalizowana w mainfunkcji. Jest używany w authMiddlewarefunkcji środkowej.

Jeśli zauważysz, pobieramy nasze poświadczenia po stronie serwera ze zmiennej środowiskowej (jeden z założeń aplikacji 12-czynnikowej ). Nasze oprogramowanie pośredniczące sprawdza i odbiera token z żądania i wywołuje jwtMiddleWare.CheckJWTmetodę, aby zweryfikować wysłany token.

Napiszmy też funkcję zwracającą klucze sieciowe JSON:

// ... the code above is untouched... // Jwks stores a slice of JSON Web Keys type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } func main() { // ... the code in this method is untouched... } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key.") } return cert, nil }

Korzystanie z oprogramowania pośredniczącego JWT

Korzystanie z oprogramowania pośredniego jest bardzo proste. Po prostu przekazujemy go jako parametr do naszej definicji tras.

... api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) ...

Nasz main.goplik powinien wyglądać następująco:

package main import ( "encoding/json" "errors" "fmt" "log" "net/http" "os" "strconv" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) type Response struct { Message string `json:"message"` } type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } /** we'll create a list of jokes */ var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) jwtMiddleWare = jwtMiddleware // Set the router as the default one shipped with Gin router := gin.Default() // Serve the frontend router.Use(static.Serve("/", static.LocalFile("./views", true))) api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) } // Start the app router.Run(":3000") } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key") } return cert, nil } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } } // JokeHandler returns a list of jokes available (in memory) func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } func LikeJoke(c *gin.Context) { // Check joke ID is valid if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes = jokes[i].Likes + 1 } } c.JSON(http.StatusOK, &jokes) } else { // the jokes ID is invalid c.AbortWithStatus(http.StatusNotFound) } }

Zainstalujmy jwtmiddlewarebiblioteki:

$ go get -u github.com/auth0/go-jwt-middleware $ go get -u github.com/dgrijalva/jwt-go

Zbierzmy nasz plik środowiska i zrestartujmy nasz serwer aplikacji:

$ source .env $ go run main.go

Teraz, jeśli spróbujemy uzyskać dostęp do dowolnego punktu końcowego, napotkamy 401 Unauthorizedbłąd. Dzieje się tak, ponieważ wraz z żądaniem musimy przesłać token.

Zaloguj się za pomocą Auth0 i React

Zaimplementujmy system logowania, aby użytkownicy mogli się logować lub tworzyć konta i uzyskiwać dostęp do naszych żartów. app.jsxDodamy do naszego pliku następujące dane uwierzytelniające Auth0:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL - adres URL Twojej aplikacji
  • AUTH0_API_AUDIENCE
Można znaleźć AUTH0_CLIENT_ID, AUTH0_DOMAINoraz AUTH0_API_AUDIENCEdane z panelu zarządzania Auth0.

Musimy ustawić, do callbackktórego Auth0 przekierowuje. Przejdź do sekcji Klienci na pulpicie nawigacyjnym. W ustawieniach ustawmy oddzwonienie na //localhost:3000:

Mając dane uwierzytelniające, zaktualizujmy nasze komponenty React.

Składnik aplikacji

const AUTH0_CLIENT_ID = "aIAOt9fkMZKrNsSsFqbKj5KTI0ObTDPP"; const AUTH0_DOMAIN = "hakaselabs.auth0.com"; const AUTH0_CALLBACK_URL = location.href; const AUTH0_API_AUDIENCE = "golang-gin"; class App extends React.Component { parseHash() { this.auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID }); this.auth0.parseHash(window.location.hash, (err, authResult) => { if (err) { return console.log(err); } if ( authResult !== null && authResult.accessToken !== null && authResult.idToken !== null ) { localStorage.setItem("access_token", authResult.accessToken); localStorage.setItem("id_token", authResult.idToken); localStorage.setItem( "profile", JSON.stringify(authResult.idTokenPayload) ); window.location = window.location.href.substr( 0, window.location.href.indexOf("#") ); } }); } setup() { $.ajaxSetup({ beforeSend: (r) => { if (localStorage.getItem("access_token")) { r.setRequestHeader( "Authorization", "Bearer " + localStorage.getItem("access_token") ); } } }); } setState() { let idToken = localStorage.getItem("id_token"); if (idToken) { this.loggedIn = true; } else { this.loggedIn = false; } } componentWillMount() { this.setup(); this.parseHash(); this.setState(); } render() { if (this.loggedIn) { return ; } return ; } }

Zaktualizowaliśmy komponent aplikacji z trzech metod składowych ( setup, parseHash, i setState) oraz sposób cyklem życia componentWillMount. parseHashMetoda inicjuje auth0webAuthklient i analizuje hash do formatu bardziej czytelny, zapisując je do localSt. Aby wyświetlić ekran blokady, przechwyć i zapisz token użytkownika oraz dodaj poprawny nagłówek autoryzacji do wszelkich żądań do naszego API

Komponent domowy

Nasz komponent Home zostanie zaktualizowany. Dodamy funkcję do authenticatemetody, która spowoduje wyświetlenie hostowanego ekranu blokady i umożliwi naszym użytkownikom logowanie lub rejestrację.

class Home extends React.Component { constructor(props) { super(props); this.authenticate = this.authenticate.bind(this); } authenticate() { this.WebAuth = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID, scope: "openid profile", audience: AUTH0_API_AUDIENCE, responseType: "token id_token", redirectUri: AUTH0_CALLBACK_URL }); this.WebAuth.authorize(); } render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ); } }

Składnik LoggedIn

Zaktualizujemy LoggedInkomponent, aby komunikował się z naszym API i wyciągniemy wszystkie żarty. Przekaże każdy żart jako a propdo Jokekomponentu, który renderuje panel ładowania początkowego. Napiszmy te:

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] }; this.serverRequest = this.serverRequest.bind(this); this.logout = this.logout.bind(this); } logout() { localStorage.removeItem("id_token"); localStorage.removeItem("access_token"); localStorage.removeItem("profile"); location.reload(); } serverRequest() { $.get("//localhost:3000/api/jokes", res => { this.setState({ jokes: res }); }); } componentDidMount() { this.serverRequest(); } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i) { return ; })} ); } }

Komponent żartu

Zaktualizujemy również Jokekomponent, aby formatował każdy element Joke przekazany do niego z komponentu Parent ( LoggedIn). Dodamy również likemetodę, która zwiększy liczbę żartów.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "", jokes: [] }; this.like = this.like.bind(this); this.serverRequest = this.serverRequest.bind(this); } like() { let joke = this.props.joke; this.serverRequest(joke); } serverRequest(joke) { $.post( "//localhost:3000/api/jokes/like/" + joke.id, { like: 1 }, res => { console.log("res... ", res); this.setState({ liked: "Liked!", jokes: res }); this.props.jokes = res; } ); } render() { return ( #{this.props.joke.id}{" "} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Kładąc wszystko razem

Po ukończeniu interfejsu użytkownika i interfejsu API możemy przetestować naszą aplikację. Zaczniemy od uruchomienia naszego serwera source .env && go run main.go, a następnie przejdziemy do //localhost:3000dowolnej przeglądarki. Powinien pojawić się Homekomponent z przyciskiem logowania. Kliknięcie przycisku logowania spowoduje przekierowanie do hostowanej strony blokady (utwórz konto lub zaloguj się), aby kontynuować korzystanie z aplikacji.

Dom:

Hostowany ekran blokady Auth0:

Widok zalogowanych w aplikacji:

Wniosek

Gratulacje! Dowiedziałeś się, jak zbudować aplikację i API za pomocą Go i frameworka Gin.

Czy przegapiłem coś ważnego? Dajcie znać w komentarzach.

Możesz przywitać się ze mną na Twitterze @codehakase