de Francis Sunday

Cum să creați o aplicație web cu Go, Gin și React

Cum sa creati o aplicatie web cu Go Gin si

Acest articol a fost inițial postat pe Blogul meu

TL; DR: În acest tutorial, vă voi arăta cât de ușor este să creați o aplicație web cu Go și cadrul Gin și să adăugați autentificare. Consultați Github repo pentru codul pe care îl vom scrie.

Gin este un micro-cadru performant. Oferă un cadru foarte minimalist care poartă cu sine doar cele mai esențiale caracteristici, biblioteci și funcționalități necesare pentru a construi aplicații web și microservicii. Este simplă construirea unei conducte de manipulare a cererilor din piese modulare, refolosibile. Face acest lucru permițându-vă să scrieți middleware care poate fi conectat la unul sau mai multe gestionare de cereri sau grupuri de gestionare de cereri.

Caracteristici Gin

Gin este un cadru web rapid, simplu, dar complet caracteristic și foarte eficient pentru Go. Consultați câteva dintre caracteristicile de mai jos care îl fac un cadru demn de luat în considerare pentru următorul dvs. proiect Golang.

  • Viteză: Gin este construit pentru viteză. Cadrul oferă o rutare bazată pe arborele Radix și o mică amprentă de memorie. Fără reflecție. Performanță API previzibilă.
  • Fără Crash: Gin are capacitatea de a prinde accidente sau panici în timpul rulării și se poate recupera de la ele. În acest fel aplicația dvs. va fi întotdeauna disponibilă.
  • Rutare: Gin oferă o interfață de rutare care vă permite să exprimați cum ar trebui să arate aplicația dvs. web sau rutele API.
  • Validare JSON: Gin poate analiza și valida cu ușurință solicitările JSON, verificând existența valorilor necesare.
  • Gestionarea erorilor: Gin oferă o modalitate convenabilă de a colecta toate erorile apărute în timpul unei solicitări HTTP. În cele din urmă, un middleware le poate scrie într-un fișier jurnal sau într-o bază de date și le poate trimite prin rețea.
  • Redare încorporată: Gin oferă un API ușor de utilizat pentru redarea JSON, XML și HTML.

Condiții prealabile

Pentru a urma acest tutorial, va trebui să aveți Go instalat pe computerul dvs., un browser web pentru a vizualiza aplicația și o linie de comandă pentru a executa comenzile de construire.

Merge, sau așa cum se numește în mod normal Golang, este un limbaj de programare dezvoltat de Google pentru a construi software modern. Go este un limbaj conceput pentru a realiza lucrurile eficient și rapid. Principalele avantaje ale Go includ:

  • Tastat puternic și gunoi colectat
  • Timpi de compilare rapidi
  • Concurență încorporată
  • Bibliotecă standard extinsă

Mergeți la secțiunea de descărcări a site-ului Go pentru a porni Go rulează pe computerul dvs.

Construirea unei aplicații cu Gin

Vom construi o aplicație simplă de listare a glumelor cu Gin. Aplicația noastră va enumera câteva glume tâmpite. Vom adăuga autentificare, astfel încât toți utilizatorii conectați să aibă privilegiul de a aprecia și de a vizualiza glumele.

Acest lucru ne va permite să ilustrăm cum Gin poate fi folosit pentru a dezvolta aplicații web și / sau API-uri.

1611968530 540 Cum sa creati o aplicatie web cu Go Gin si

Vom folosi următoarele funcționalități oferite de Gin:

  • Middleware
  • Rutare
  • Gruparea rutelor

Pe locuri, fiti gata, start

Vom scrie întreaga noastră aplicație Go într-un main.go fişier. Deoarece este o aplicație mică, va fi ușor să creați aplicația doar go run de la terminal.

Vom crea un nou director golang-gin în spațiul nostru de lucru Go, apoi a main.go fișier în acesta:

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

Conținutul main.go fişier:

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")
}

Va trebui să creăm câteva directoare pentru fișierele noastre statice. În același director ca main.go fișier, să creăm un fișier views pliant. În views folder, creați un js folder și un index.html fișier în el.

index.html fișierul va fi foarte simplu pentru moment:

<!DOCTYPE html>
<html>
<head>
  <title>Jokeish App</title>
</head>

<body>
  <h1>Welcome to the Jokeish App</h1>
</body>
</html>

Înainte de a testa ceea ce avem până acum, să instalăm dependențele adăugate:

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

Pentru a vedea ce funcționează, va trebui să pornim serverul rulând go run main.go.

1611968531 195 Cum sa creati o aplicatie web cu Go Gin si

Odată ce aplicația rulează, navigați la http://localhost:3000 în browserul dvs. Dacă totul a mers bine, ar trebui să vedeți textul antetului de nivelul 1 Bine ați venit la aplicația Jokeish afișat.

1611968531 844 Cum sa creati o aplicatie web cu Go Gin si

Definirea API-ului

Să adăugăm mai multe coduri în main.go pentru definițiile API-ului nostru. Ne vom actualiza main funcționează cu două rute /jokes/ și /jokes/like/:jokeID la grupul de traseu /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",
  })
}

Conținutul main.go fișierul ar trebui să arate astfel:

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",
  })
}

Să rulăm din nou aplicația noastră go run main.goși accesați rutele noastre. http://localhost:3000/api/jokes va returna un 200 OK răspuns antet, cu mesajul jokes handler not implemented yet. O cerere POST către http://localhost:3000/api/jokes/like/1 returnează un 200 OK antetul și mesajul Likejoke handler not implemented yet.

Date despre glume

Deoarece avem deja setul nostru de definire a rutelor, care face doar un singur lucru (returnăm un răspuns JSON), ne vom condimenta puțin baza de coduri adăugându-i ceva mai mult cod.

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

Codul nostru arată bine, să mergem mai departe și să testăm API-ul nostru. Putem testa cu cURL sau postman , apoi trimiteți un GET solicită să http://localhost:3000/jokes pentru a obține lista completă a glumelor și a POST solicită să http://localhost:3000/jokes/like/{jokeid} pentru a crește gusturile unei glume.

$ curl http://localhost:3000/api/jokes

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

Construirea UI (React)

Avem API-ul nostru în poziție, așa că haideți să construim un frontend pentru a prezenta datele din API-ul nostru. Pentru aceasta, vom folosi React. Nu vom intra prea adânc în React, deoarece va ieși din sfera acestui tutorial. Dacă trebuie să aflați mai multe despre React, verificați oficialul tutorial. Puteți implementa interfața de utilizare cu orice cadru frontend cu care vă simțiți confortabil.

Înființat

Vom edita fișierul index.html pentru a adăuga bibliotecile externe necesare pentru a rula React. Apoi va trebui să creăm un app.jsx fișier în views/js director, care va conține codul nostru React.

Al nostru index.html fișierul ar trebui să arate astfel:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <title>Jokeish App</title>
  <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
  <script src="https://cdn.auth0.com/js/auth0/9.0/auth0.min.js"></script>
  <script type="application/javascript" src="https://unpkg.com/react@16.0.0/umd/react.production.min.js"></script>
  <script type="application/javascript" src="https://unpkg.com/react-dom@16.0.0/umd/react-dom.production.min.js"></script>
  <script type="application/javascript" src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  <script type="text/babel" src="js/app.jsx"></script>
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
</head>

<body>
  <div id="app"></div>
</body>

</html>

Construirea componentelor noastre

În React, vizualizările sunt împărțite în componente. Va trebui să construim câteva componente:

  • un App componentă ca intrare principală care lansează aplicația
  • A Home componentă care se va confrunta cu utilizatori non-conectați
  • A LoggedIn componentă cu conținut vizibil numai de către utilizatorii autentificați
  • și a Joke componentă pentru a afișa o listă de glume.

Vom scrie toate aceste componente în app.jsx fişier.

Componenta aplicației

Această componentă pornește întreaga noastră aplicație React. Acesta decide ce componentă să afișeze în funcție de faptul dacă un utilizator este sau nu autentificat. Vom începe doar cu baza sa și o vom actualiza ulterior cu mai multe funcționalități.

class App extends React.Component {
  render() {
    if (this.loggedIn) {
      return (<LoggedIn />);
    } else {
      return (<Home />);
    }
  }
}

Componenta Acasă

Această componentă este afișată utilizatorilor care nu s-au conectat, împreună cu un buton care deschide un ecran de blocare găzduit unde se pot înregistra sau autentifica. Vom adăuga această funcționalitate mai târziu.

class Home extends React.Component {
  render() {
    return (
      <div className="container">
        <div className="col-xs-8 col-xs-offset-2 jumbotron text-center">
          <h1>Jokeish</h1>
          <p>A load of Dad jokes XD</p>
          <p>Sign in to get access </p>
          <a onClick={this.authenticate} className="btn btn-primary btn-lg btn-login btn-block">Sign In</a>
        </div>
      </div>
    )
  }
}

Componenta LoggedIn

Această componentă este afișată atunci când un utilizator este autentificat. Se stochează în state o serie de glume care este populată atunci când componenta se montează.

class LoggedIn extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      jokes: []
    }
  }
    
  render() {
    return (
      <div className="container">
        <div className="col-lg-12">
          <br />
          <span className="pull-right"><a onClick={this.logout}>Log out</a></span>
          <h2>Jokeish</h2>
          <p>Let's feed you with some funny Jokes!!!</p>
          <div className="row">
            {this.state.jokes.map(function(joke, i){
              return (<Joke key={i} joke={joke} />);
            })}
          </div>
        </div>
      </div>
    )
  }
}

Componenta Glumă

Joke componenta va conține informații despre fiecare articol din răspunsul la glume care va fi afișat.

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 (
      <div className="col-xs-4">
        <div className="panel panel-default">
          <div className="panel-heading">#{this.props.joke.id} <span className="pull-right">{this.state.liked}</span></div>
          <div className="panel-body">
            {this.props.joke.joke}
          </div>
          <div className="panel-footer">
            {this.props.joke.likes} Likes &nbsp;
            <a onClick={this.like} className="btn btn-default">
              <span className="glyphicon glyphicon-thumbs-up"></span>
            </a>
          </div>
        </div>
      </div>
    )
  }
}

Am scris componentele noastre, așa că acum să spunem React unde să redea aplicația. Vom adăuga blocul de cod de mai jos în partea de jos a paginii noastre app.jsx fişier.

ReactDOM.render(<App />, document.getElementById('app'));

Să repornim serverul Go go run main.goși accesați adresa URL a aplicației noastre http://localhost:3000/. Veți vedea că Home componentă este redată.

1611968531 576 Cum sa creati o aplicatie web cu Go Gin si

Securizarea aplicației noastre de glume cu Auth0

Auth0 probleme Jetonuri web JSON la fiecare conectare pentru utilizatorii dvs. Acest lucru înseamnă că puteți avea un solid infrastructura de identitate, inclusiv o singură logare, gestionarea utilizatorilor, asistență pentru furnizorii de identitate socială (Facebook, Github, Twitter etc.), furnizorii de identitate pentru întreprinderi (Active Directory, LDAP, SAML etc.) și propria bază de date de utilizatori, cu doar câteva linii de cod.

Putem configura cu ușurință autentificarea în aplicația noastră GIN utilizând Auth0. Veți avea nevoie de un cont pentru a urma împreună cu această parte. Dacă nu aveți deja un cont Auth0, Inscrie-te pentru unul acum.

Declinare de responsabilitate: Acest conținut nu este sponsorizat.

Crearea clientului API

Jetoanele noastre vor fi generate cu Auth0, deci trebuie să creăm un API și un Client din tabloul nostru de bord Auth0. Din nou, dacă nu ați făcut-o deja, Inscrie-te pentru un cont Auth0.

Pentru a crea un nou API, navigați la Secțiunea API în tabloul de bord și faceți clic pe Creați API buton.

1611968531 275 Cum sa creati o aplicatie web cu Go Gin si

Alegeți un API Nume si un identificator. Identificatorul va fi public pentru middleware. Algoritm de semnare ar trebui să fie RS256.

Pentru a crea un client nou, navigați la secțiunea clienți în tabloul de bord și faceți clic pe Creați client buton. Selectați tipul Regular Web Applications.

1611968532 825 Cum sa creati o aplicatie web cu Go Gin si

Odată ce clientul este creat, luați notă de client_id și client_secret, deoarece vom avea nevoie de ele mai târziu.

1611968532 690 Cum sa creati o aplicatie web cu Go Gin si

Trebuie să adăugăm acreditările necesare pentru API-ul nostru la o variabilă de mediu. În directorul rădăcină, creați un fișier nou .env și adăugați următoarele, cu detaliile din tabloul de bord Auth0:

export AUTH0_API_CLIENT_SECRET=""
export AUTH0_CLIENT_ID=""
export AUTH0_DOMAIN="yourdomain.auth0.com"
export AUTH0_API_AUDIENCE=""

Securizarea punctelor noastre finale API

În prezent, API-ul nostru este deschis lumii. Trebuie să ne securizăm punctele finale, astfel încât numai utilizatorii autorizați să le poată accesa.

Vom folosi un JWT Middleware pentru a verifica un valabil JSON Web Token de la fiecare solicitare care ne atinge obiectivele finale.

Să creăm middleware-ul nostru:

// ...

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
    }
  }
}

În codul de mai sus, avem un nou jwtMiddleWare variabilă care este inițializată în main funcţie. Este folosit în authMiddleware funcția de mijloc.

Dacă observați, extragem acreditările noastre de pe server dintr-o variabilă de mediu (una dintre principiile unui Aplicație cu 12 factori). Middleware-ul nostru verifică și primește un jeton de la o cerere și apelează jwtMiddleWare.CheckJWT metoda de validare a simbolului trimis.

Să scriem și funcția de returnare a cheilor web 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
}

Utilizarea middleware-ului JWT

Utilizarea middleware-ului este foarte simplă. Trecem doar ca parametru la definiția rutelor noastre.

...

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

...

Al nostru main.go fișierul ar trebui să arate astfel:

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)
  }
}

Să instalăm fișierul jwtmiddleware biblioteci:

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

Să ne descărcăm fișierul de mediu și să repornim serverul de aplicații:

$ source .env
$ go run main.go

Acum, dacă încercăm să accesăm oricare dintre punctele finale, ne vom confrunta cu un 401 Unauthorized eroare. Acest lucru se datorează faptului că trebuie să trimitem un simbol cu ​​cererea.

Conectați-vă cu Auth0 și React

Să implementăm un sistem de autentificare, astfel încât utilizatorii să se poată autentifica sau să creeze conturi și să aibă acces la glumele noastre. Vom adăuga la al nostru app.jsx depuneți următoarele acreditări Auth0:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL – Adresa URL a aplicației dvs.
  • AUTH0_API_AUDIENCE

Puteți găsi AUTH0_CLIENT_ID, AUTH0_DOMAIN, și AUTH0_API_AUDIENCE date din Auth0 tabloul de bord de management.

Trebuie să stabilim un callback către care Auth0 redirecționează. Navigați la secțiunea Clienți din tabloul de bord. În setări, să setăm apelul invers http://localhost:3000:

1611968532 661 Cum sa creati o aplicatie web cu Go Gin si

Cu acreditările în vigoare, să ne actualizăm componentele React.

Componenta APP

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 <LoggedIn />;
    }
    return <Home />;
  }
}

Am actualizat componenta aplicației cu trei metode componente (setup, parseHash, și setState) și o metodă a ciclului de viață componentWillMount. parseHash metoda inițializează auth0 webAuth client și analizează hashul într-un format mai lizibil, salvându-le în localSt. Pentru a afișa ecranul de blocare, capturați și stocați jetonul utilizatorului și adăugați antetul de autorizare corect la orice solicitare a API-ului nostru

Componenta de acasă

Componenta noastră de acasă va fi actualizată. Vom adăuga funcționalitatea pentru authenticate , care va declanșa afișarea ecranului de blocare găzduit și va permite utilizatorilor noștri să se conecteze sau să se înscrie.

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 (
      <div className="container">
        <div className="row">
          <div className="col-xs-8 col-xs-offset-2 jumbotron text-center">
            <h1>Jokeish</h1>
            <p>A load of Dad jokes XD</p>
            <p>Sign in to get access </p>
            <a
              onClick={this.authenticate}
              className="btn btn-primary btn-lg btn-login btn-block"
            >
              Sign In
            </a>
          </div>
        </div>
      </div>
    );
  }
}

Componenta LoggedIn

Vom actualiza LoggedIn componentă pentru a comunica cu API-ul nostru și pentru a trage toate glumele. Va trece fiecare glumă ca a prop la Joke componentă, care redă un panou bootstrap. Să le scriem pe acestea:

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("http://localhost:3000/api/jokes", res => {
      this.setState({
        jokes: res
      });
    });
  }
  
  componentDidMount() {
    this.serverRequest();
  }
  
  render() {
    return (
      <div className="container">
        <br />
        <span className="pull-right">
          <a onClick={this.logout}>Log out</a>
        </span>
        <h2>Jokeish</h2>
        <p>Let's feed you with some funny Jokes!!!</p>
        <div className="row">
          <div className="container">
            {this.state.jokes.map(function(joke, i) {
              return <Joke key={i} joke={joke} />;
            })}
          </div>
        </div>
      </div>
    );
  }
}

Componenta glumă

Vom actualiza și fișierul Joke componentă pentru formatarea fiecărui element de glumă transmis din componenta părinte (LoggedIn). Vom adăuga și un like , care va crește gradul de glumă.

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(
      "http://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 (
      <div className="col-xs-4">
        <div className="panel panel-default">
          <div className="panel-heading">
            #{this.props.joke.id}{" "}
            <span className="pull-right">{this.state.liked}</span>
          </div>
          <div className="panel-body">{this.props.joke.joke}</div>
          <div className="panel-footer">
            {this.props.joke.likes} Likes &nbsp;
            <a onClick={this.like} className="btn btn-default">
              <span className="glyphicon glyphicon-thumbs-up" />
            </a>
          </div>
        </div>
      </div>
    )
  }
}

Punând totul împreună

Cu UI și API complete, putem testa aplicația noastră. Vom începe pornind serverul nostru source .env && go run main.go, și apoi vom naviga la http://localhost:3000 din orice browser. Ar trebui să vedeți Home componentă cu un buton de conectare. Dacă faceți clic pe butonul de conectare, veți redirecționa către o pagină de blocare găzduită (creați un cont sau conectați-vă) pentru a continua să utilizați aplicația.

Acasă:

1611968533 996 Cum sa creati o aplicatie web cu Go Gin si

Ecran de blocare găzduit Auth0:

1611968533 82 Cum sa creati o aplicatie web cu Go Gin si

Vizualizare aplicație conectată:

1611968533 899 Cum sa creati o aplicatie web cu Go Gin si

Concluzie

Felicitări! Ați învățat cum să creați o aplicație și un API cu Go și cadrul Gin.

Mi-a fost dor de ceva important? Anunță-mă în comentarii.

Poți să-mi spui Bună ziua pe Twitter @codehakase