paint-brush
Creación de un Middleware en Golang para autenticación basada en JWTpor@agzuniverse
20,043 lecturas
20,043 lecturas

Creación de un Middleware en Golang para autenticación basada en JWT

por Aswin G7m2020/04/15
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Creación de un middleware en Golang para la autenticación basada en JWT: "Autenticación" En esta publicación, repasaré cómo crear un middleware de autenticación para Golang que puede restringir ciertas partes de su aplicación web para que requieran autenticación. El alcance de este artículo se limita a la creación de un middleware para verificar la validez de un JWT en una solicitud entrante. También puede aplicar este patrón para la creación de middlewares para hacer cualquier middleware que haga lo que quiera. Si el JWT no está presente o está dañado, la aplicación debe devolver el código de estado HTTP 401: no autorizado.

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Creación de un Middleware en Golang para autenticación basada en JWT
Aswin G HackerNoon profile picture

Golang ha sido un lenguaje popular en los últimos años conocido por su simplicidad y excelente soporte listo para usar para crear aplicaciones web y para el procesamiento pesado de concurrencia. Del mismo modo, JWT (JSON Web Tokens) se está convirtiendo en una forma cada vez más popular de autenticar a los usuarios. En esta publicación, repasaré cómo crear un middleware de autenticación para Golang que pueda restringir ciertas partes de su aplicación web para que requieran autenticación.

También puede aplicar este patrón para la creación de middlewares para hacer cualquier middleware que haga lo que quiera.

Si no está familiarizado con los JWT, https://jwt.io/ es un excelente recurso para familiarizarse con ellos. Básicamente, un JWT es un token incluido en el

 Authorization
El encabezado de una solicitud HTTP se puede usar para verificar al usuario que realiza la solicitud. Si bien la mayoría de las formas de autenticación de token requieren una lectura de la base de datos para verificar que el token pertenece a un usuario autenticado activo, al usar JWT, si el JWT se puede decodificar correctamente, eso garantiza que es un token válido, ya que tiene un campo de firma que se convertirá en no válido si algún dato en el token está dañado o manipulado. También puede decidir qué datos codificar en el cuerpo de JWT, lo que significa que al decodificar con éxito un JWT también puede obtener datos útiles, como el nombre de usuario o el correo electrónico de un usuario.

El alcance de este artículo se limita a la creación de un middleware en Golang para verificar la validez de un JWT en una solicitud entrante. La generación de JWT es un proceso separado y no describiré cómo hacerlo aquí.

Configuración de la aplicación web

Estoy usando una aplicación web simple de Golang que proporciona un punto final de API único:

 /ping
, que responde con "pong". No se han utilizado bibliotecas en el siguiente código.

 package main import ( "log" "net/http" ) func pong (w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([] byte ( "pong" )) } func main () { http.Handle( "/ping" , http.HandlerFunc(pong)) log.Fatal(http.ListenAndServe( ":8080" , nil )) }

Aquí hay un desglose rápido de lo que esto hace:

La función principal registra

 pong
como la función de controlador para el
 /ping
punto final Luego inicia un servidor HTTP que se ejecuta en el puerto 8080. La función
 pong
simplemente responde con un estado de 200 (que significa "OK") y el texto "pong".
 Handle
requiere que su segundo argumento sea un valor que implemente el
 Handler
interfaz, por lo que
 HandlerFunc
es una función de adaptador que hace precisamente eso. Más sobre esto más adelante.

Así que ahora si ejecutas esto con

 go run main.go
(Suponiendo que el nombre del archivo es
 main.go
) y envíe una solicitud GET a
 http://localhost:8080/ping
(Abrir este enlace en su navegador es una manera fácil) obtendrá el texto
 pong
.

Ahora quiero proteger el

 /ping
punto final para que solo las solicitudes entrantes que tengan un JWT válido puedan obtener la respuesta requerida. Si el JWT no está presente o está dañado, la aplicación debe devolver el código de estado HTTP 401: no autorizado.

Creación de un software intermedio

Un middleware para nuestro servidor HTTP debería ser una función que admita una función que implemente el

 http.Handler
interfaz y devuelve una nueva función que implementa la
 http.Handler
interfaz.

Este concepto debería ser familiar para cualquier persona que haya usado cierres de JavaScript, decoradores de Python o programación funcional de algún tipo. Pero esencialmente, es una función que toma una función y le agrega funcionalidad adicional.

Pero espera, la función de controlador que acabamos de escribir,

 pong
, no implementa esta interfaz. Así que tenemos que cuidar eso. Si miras en el integrado de Golang
 http
paquete encontrará que el
 Handler
interfaz solo especifica que el
 ServeHTTP
debe implementarse el método para ese valor. De hecho, ni siquiera tenemos que implementar esto nosotros mismos para nuestra función de controlador.
 http
también proporciona una práctica función auxiliar llamada
 HandlerFunc
que acepta cualquier función que acepte
 ResponseWriter
y
 *Request
como parámetros, y lo convierte en una función que implementa el
 Handler
interfaz.

En este punto es fácil confundirse entre estas 4 cosas en el

 http
paquete, así que permítanme aclararlos:

 http.Handler
- Una interfaz con un solo miembro -
 ServeHTTP
.

 http.HandlerFunc
- Una función que se utiliza para convertir
 pong
(y cualquier otra función de controlador) a una función que implementa el
 Handler
interfaz.

 http.HandleFunc
- Una función utilizada para asociar un punto final con una función de controlador. Convierte automáticamente la función del controlador en una función que implementa la interfaz del controlador. No hemos usado esto aquí.

 http.Handle
- Igual que
 HandleFunc
, excepto que NO convierte la función del controlador en una función que implementa el
 Handler
interfaz.

Vamos a crear la estructura de un middleware básico:

 func middleware (next http.Handler) http . Handler { return http.HandlerFunc( func (w http.ResponseWriter, r *http.Request) { // Do stuff next.ServeHTTP(w, r) }) }

Como puede ver, recibimos la solicitud y podemos hacer lo que queramos con ella antes de llamar a la función del controlador que creamos anteriormente, que se pasa a nuestro middleware como

 next
. hacemos uso de
 HandlerFunc
de nuevo para convertir la función volvemos a una función que implementa
 Handler
interfaz.

Ahora puede hacer que su middleware haga lo que quiera con la solicitud entrante. Lo que quiero hacer es validar el JWT en el

 Authorization
encabezado de la solicitud, así que comenzaré con los siguientes cambios:

 authHeader := strings.Split(r.Header.Get( "Authorization" ), "Bearer " ) if len (authHeader) != 2 { fmt.Println( "Malformed token" ) w.WriteHeader(http.StatusUnauthorized) w.Write([] byte ( "Malformed Token" )) }

Esto toma el token JWT del

 Authorization
encabezamiento. Si el token no está presente, devuelve un estado no autorizado y nunca llama a nuestra función de controlador.

El formato esperado del encabezado es

 Bearer <token>
. Tenga en cuenta que puede pasar el JWT en la solicitud de la forma que desee, pero esta es la forma ampliamente aceptada de hacerlo.

Si el token está realmente presente, tendremos que decodificarlo. Para esto estoy usando el

 go-jwt
biblioteca, que se puede instalar con
 go get github.com/dgrijalva/jwt-go

 else { jwtToken := authHeader[ 1 ] token, err := jwt.Parse(jwtToken, func (token *jwt.Token) ( interface {}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil , fmt.Errorf( "Unexpected signing method: %v" , token.Header[ "alg" ]) } return [] byte (SECRETKEY), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx := context.WithValue(r.Context(), "props" , claims) // Access context values in handlers like this // props, _ := r.Context().Value("props").(jwt.MapClaims) next.ServeHTTP(w, r.WithContext(ctx)) } else { fmt.Println(err) w.WriteHeader(http.StatusUnauthorized) w.Write([] byte ( "Unauthorized" )) } }

Usamos

 jwt.Parse
para decodificar nuestro token. El segundo argumento de esta función es una función que se utiliza para devolver la clave secreta utilizada para decodificar el token después de verificar si el método de firma del token es HMAC. Esto se debe a que JWT se puede codificar de muchas maneras, incluido el cifrado asimétrico con un par de claves públicas y privadas. Aquí estoy usando una clave secreta simple para decodificar el JWT. Esta debe ser la misma clave secreta utilizada para codificar el JWT por la entidad que generó el JWT. El método de firma no será HMAC si la clave se codificó de alguna otra manera, por lo que verificamos esto primero.

Tenga en cuenta que debe reemplazar el

 SECRETKEY
variable con su clave secreta, que debe ser una cadena.

Luego obtenemos los reclamos del token. Los reclamos son los valores que se han codificado en el JWT. Si la decodificación falla, significa que el token se ha dañado o manipulado y devolvemos un estado no autorizado.

Si la decodificación fue exitosa, creamos una variable

 ctx
para mantener estos reclamos y adjuntarlos a la instancia de solicitud a través de su
 Context
. Esto se hace para que podamos acceder a ellos en nuestra función de controlador (u otros middlewares si encadenamos varios middlewares) si es necesario, como se menciona en las líneas comentadas. "accesorios" aquí es una clave que utilicé, y se puede sustituir con cualquier valor de su elección siempre que use la misma clave mientras intenta obtener datos del contexto. Un caso de uso típico sería obtener el ID de usuario de las notificaciones en la función del controlador y usarlo para realizar una operación de base de datos para obtener información que corresponda a ese usuario.

Finalmente, cambiamos nuestro código anterior para envolver la función del controlador en el middleware.

 http.Handle( "/ping" , middleware(http.HandlerFunc(pong)))

El código completo se ve así:

 package main import ( "context" "fmt" "log" "net/http" "strings" "github.com/dgrijalva/jwt-go" ) func middleware (next http.Handler) http . Handler { return http.HandlerFunc( func (w http.ResponseWriter, r *http.Request) { authHeader := strings.Split(r.Header.Get( "Authorization" ), "Bearer " ) if len (authHeader) != 2 { fmt.Println( "Malformed token" ) w.WriteHeader(http.StatusUnauthorized) w.Write([] byte ( "Malformed Token" )) } else { jwtToken := authHeader[ 1 ] token, err := jwt.Parse(jwtToken, func (token *jwt.Token) ( interface {}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil , fmt.Errorf( "Unexpected signing method: %v" , token.Header[ "alg" ]) } return [] byte (SECRETKEY), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx := context.WithValue(r.Context(), "props" , claims) // Access context values in handlers like this // props, _ := r.Context().Value("props").(jwt.MapClaims) next.ServeHTTP(w, r.WithContext(ctx)) } else { fmt.Println(err) w.WriteHeader(http.StatusUnauthorized) w.Write([] byte ( "Unauthorized" )) } } }) } func pong (w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([] byte ( "pong" )) } func main () { http.Handle( "/ping" , middleware(http.HandlerFunc(pong))) log.Fatal(http.ListenAndServe( ":8080" , nil )) }

Puede usar el mismo enfoque para crear varios middlewares y aplicarlos en los puntos finales que los necesitan.

Espero que hayas encontrado útil esta publicación.

Puedes encontrarme en Twitter y LinkedIn .