Introduzione a JWT
JWT (JSON Web Token) è un metodo conciso e autocontenuto per l'autenticazione sul web. È composto da tre parti: l'intestazione, il payload e la firma. L'intestazione contiene il tipo di token e le informazioni sull'algoritmo di cifratura, il payload contiene i dati da passare (di solito includendo alcune informazioni di autorizzazione e identificazione dell'utente) e la firma viene utilizzata per verificare l'integrità e la validità del token.
JWT è principalmente utilizzato in due scenari:
- Autenticazione: una volta effettuato l'accesso, ogni richiesta successiva includerà un JWT, consentendo all'utente di accedere a percorsi, servizi e risorse consentite.
- Scambio di informazioni: JWT è un ottimo metodo per trasmettere in modo sicuro informazioni tra le parti, in quanto possono essere firmati digitalmente, ad esempio utilizzando coppie di chiavi pubbliche/private.
Formato dei dati di JWT
JWT è un modo compatto e sicuro per rappresentare informazioni da scambiare tra le parti. Un JWT è composto effettivamente da tre parti, separate da punti (.
), ovvero Intestazione.Payload.Firma
. Successivamente dettaglieremo i formati dei dati di queste tre parti.
1. Intestazione
L'intestazione è composta tipicamente da due parti: il tipo di token, di solito JWT
, e l'algoritmo di firma o cifratura utilizzato, come ad esempio HMAC SHA256
o RSA
. L'intestazione è rappresentata in JSON e quindi codificata in una stringa utilizzando Base64Url. Ad esempio:
{
"alg": "HS256",
"typ": "JWT"
}
Dopo la codifica, si potrebbe ottenere una stringa simile a questa:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload
Il payload è composto da una serie di claim, che sono informazioni sull'entità (di solito un utente) e altri dati. Il payload può includere diversi claim predefiniti (noti anche come Claim Registrati) e claim personalizzati (Claim Privati).
I claim predefiniti possono includere:
-
iss
(Emittente): Emittente -
exp
(Tempo di scadenza): Tempo di scadenza -
sub
(Soggetto): Soggetto -
aud
(Pubblico): Pubblico -
iat
(Emesso a): Tempo di emissione -
nbf
(Non Prima di): Tempo efficace -
jti
(JWT ID): Identità unica del JWT
Un esempio di payload potrebbe assomigliare a questo (in formato JSON):
{
"sub": "1234567890",
"nome": "Mario Rossi",
"admin": true,
"iat": 1516239022
}
Anche queste informazioni verranno codificate con Base64Url e si potrebbe ottenere una stringa simile a questa:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1hcmllIFJvc3NpIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. Firma
La sezione firma viene utilizzata per firmare le due stringhe codificate menzionate sopra, al fine di verificare che il messaggio non sia stato manomesso durante la trasmissione. In primo luogo, è necessario specificare una chiave (se si utilizza l'algoritmo HMAC SHA256) e quindi utilizzare l'algoritmo specificato nell'intestazione per firmare l'intestazione e il payload.
Ad esempio, se si ha l'intestazione e il payload seguenti:
IntestazioneCodificata.PayloadCodificato
Un esempio di pseudocodice per firmarli utilizzando una chiave potrebbe essere:
HMACSHA256(base64UrlEncode(intestazione) + "." + base64UrlEncode(payload), chiaveSegreta)
La stringa firmata risultante potrebbe essere:
dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
JWT Completo
Combina queste tre parti con un punto (.) come separatore per formare il JWT completo:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1hcmllIFJvc3NpIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
Lo standard JWT è altamente flessibile e può memorizzare qualsiasi informazione necessaria nel payload (ma allo scopo di ridurre le dimensioni del JWT e per motivi di sicurezza, le informazioni sensibili non devono essere memorizzate), e garantisce l'integrità di queste informazioni tramite la firma.
Installazione della libreria JWT per Go
La comunità Go fornisce un pacchetto github.com/golang-jwt/jwt
per gestire JWT. L'installazione di questa libreria è molto semplice, basta eseguire il seguente comando nella directory del tuo progetto:
go get -u github.com/golang-jwt/jwt/v5
Una volta installata, puoi includerla nel tuo import
come segue:
import "github.com/golang-jwt/jwt/v5"
Creazione di un token semplice
Per creare un token JWT semplice utilizzando il linguaggio Go e l'algoritmo HS256, devi seguire questi passaggi:
Innanzitutto, genera un nuovo oggetto Token utilizzando l'algoritmo HS256:
token := jwt.New(jwt.SigningMethodHS256)
Successivamente, utilizza il metodo SignedString
per generare una rappresentazione in stringa di questo token, passando la chiave che verrà utilizzata per la firma.
var mySigningKey = []byte("il-tuo-segreto-da-256-bit")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("Si è verificato un errore: %v", err)
}
fmt.Println(strToken)
Questo genererà un token semplice senza alcuna claim.
Creazione di un token con parametri
Una delle funzioni principali della JWT è quella di trasportare informazioni. Queste informazioni sono codificate nel Token attraverso le claims. Ad esempio, creiamo claims personalizzate:
// Struttura Claims personalizzata
type MyClaims struct {
jwt.RegisteredClaims
Username string `json:"username"`
Admin bool `json:"admin"`
}
// Crea un token con claims personalizzate
claims := MyClaims{
RegisteredClaims: jwt.RegisteredClaims{},
Username: "il-mio-nome-utente",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Chiave segreta per la firma
var mySigningKey = []byte("il-tuo-segreto-da-256-bit")
// Genera il token in formato stringa
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("Si è verificato un errore: %v", err)
}
fmt.Println(strToken)
Nel codice sopra, abbiamo definito la struttura MyClaims
per contenere le claims registrate e alcune informazioni personalizzate. Quindi, utilizziamo ancora SignedString
per generare la stringa del token.
Analisi e convalida del token
L'analisi e la convalida del JWT sono molto importanti e puoi farlo come segue:
// Useremo la stessa struttura MyClaims
tokenString := "la-tua-stringa-JWT"
// Dobbiamo definire una funzione che il pacchetto jwt utilizzerà per analizzare la tokenString
keyFunc := func(t *jwt.Token) (interface{}, error) {
// Verifica che sia utilizzato il metodo di firma previsto
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Metodo di firma inaspettato: %v", t.Header["alg"])
}
// Restituisci la chiave segreta per il token jwt, nel formato []byte, coerente con la chiave utilizzata per la firma in precedenza
return mySigningKey, nil
}
// Analisi del token
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
log.Fatalf("Errore di analisi: %v", err)
}
if !parsedToken.Valid {
log.Fatalf("Token non valido")
}
// In questa fase, parsedToken è stato verificato e possiamo leggere le claims
fmt.Printf("Utente: %s, Admin: %v\n", claims.Username, claims.Admin)
Nel codice sopra, abbiamo fornito la stringa del token, l'istanza di MyClaims
e la funzione chiave keyFunc
alla funzione jwt.ParseWithClaims
. Questa funzione convaliderà la firma e analizzerà il token, riempiendo la variabile claims
se il token è valido.