OAuth2 è un protocollo ampiamente utilizzato che permette alle applicazioni di accedere in modo sicuro ai dati per conto degli utenti senza bisogno delle loro password. Pensalo come un sistema di chiavi elettroniche di un hotel - gli ospiti ottengono accesso temporaneo a specifiche aree senza avere la chiave master.
Goa fornisce due modi per lavorare con OAuth2:
Implementare un Provider OAuth2: Crea il tuo server di autorizzazione che emette token alle applicazioni client. È come essere l’hotel - emetti e gestisci le chiavi elettroniche.
Usare OAuth2 per Proteggere i Servizi: Proteggi i tuoi endpoint API usando token OAuth2, tipicamente da un provider esterno come Google o il tuo provider OAuth2. È come essere un negozio nell’hotel che accetta le chiavi elettroniche dell’hotel.
Esploriamo entrambi gli approcci in dettaglio.
Se vuoi creare il tuo server di autorizzazione OAuth2 (come quello di Google o GitHub), Goa fornisce un’implementazione completa attraverso il suo pacchetto goadesign/oauth2. Questa implementazione si concentra sul flusso Authorization Code, che è il flusso OAuth2 più sicuro e ampiamente utilizzato.
Quando implementi un provider OAuth2, stai creando un sistema che gestisce tre tipi principali di richieste:
Richiesta di Autorizzazione (dall’utente)
Scambio Token (dall’app client)
Refresh Token (dall’app client)
Prima, crea gli endpoint del provider OAuth2 nel tuo design. Questo codice imposta la struttura base del tuo servizio provider OAuth2:
package design
import (
. "goa.design/goa/v3/dsl"
. "github.com/goadesign/oauth2" // Importa il pacchetto provider OAuth2
)
var _ = API("oauth2_provider", func() {
Title("API Provider OAuth2")
Description("Implementazione server di autorizzazione OAuth2")
})
var OAuth2Provider = OAuth2("/oauth2/authorize", "/oauth2/token", func() {
Description("Endpoint provider OAuth2")
// Configura il flusso authorization code
AuthorizationCodeFlow("/auth", "/token", "/refresh")
// Definisci gli scope disponibili
Scope("api:read", "Accesso in lettura all'API")
Scope("api:write", "Accesso in scrittura all'API")
})
Questo codice di design:
L’interfaccia Provider è il cuore della tua implementazione OAuth2. Definisce i metodi core che gestiscono il flusso OAuth2:
type Provider interface {
// Authorize gestisce la richiesta iniziale di permessi
Authorize(clientID, scope, redirectURI string) (code string, err error)
// Exchange scambia il codice di autorizzazione con i token
Exchange(clientID, code, redirectURI string) (refreshToken, accessToken string,
expiresIn int, err error)
// Refresh fornisce nuovi token di accesso
Refresh(refreshToken, scope string) (newRefreshToken, accessToken string,
expiresIn int, err error)
// Authenticate verifica le credenziali client
Authenticate(clientID, clientSecret string) error
}
Ogni metodo serve uno scopo specifico:
Authorize
: Chiamato quando un utente approva l’accesso, genera un codice temporaneoExchange
: Converte il codice temporaneo in token di accesso e refreshRefresh
: Emette nuovi token di accesso quando quelli vecchi scadonoAuthenticate
: Valida le credenziali client prima di qualsiasi operazione sui tokenIl controller connette i tuoi endpoint HTTP alla tua implementazione Provider:
func NewOAuth2ProviderController(service *goa.Service, provider oauth2.Provider) *OAuth2ProviderController {
return &OAuth2ProviderController{
ProviderController: oauth2.NewProviderController(service, provider),
}
}
Questo controller:
Quando implementi un provider OAuth2, hai bisogno di robuste misure di sicurezza. Ecco i componenti chiave con le loro implementazioni:
Il TokenStore fornisce archiviazione e gestione sicura dei token di accesso e refresh:
type TokenStore struct {
accessTokens map[string]*TokenInfo
refreshTokens map[string]*TokenInfo
mu sync.RWMutex
}
func (s *TokenStore) StoreToken(info *TokenInfo) error {
s.mu.Lock()
defer s.mu.Unlock()
s.accessTokens[info.AccessToken] = info
if info.RefreshToken != "" {
s.refreshTokens[info.RefreshToken] = info
}
return nil
}
Questa implementazione:
La struct Client gestisce le informazioni sui client OAuth2 registrati:
type Client struct {
ID string // Identificatore unico per il client
Secret string // Chiave segreta del client per l'autenticazione
RedirectURI string // URI di reindirizzamento autorizzato
Scopes []string // Scope permessi per questo client
Type string // "confidential" o "public"
}
Questa struttura:
Se vuoi proteggere i tuoi endpoint API usando OAuth2 (sia il tuo provider che uno esterno come Google), Goa rende questo processo semplice.
Questo codice dice a Goa come proteggere la tua API con OAuth2:
package design
import (
. "goa.design/goa/v3/dsl"
)
var OAuth2Auth = OAuth2Security("oauth2", func() {
Description("Autenticazione OAuth2")
// Definisci quali flussi OAuth2 supporti
AuthorizationCodeFlow("/auth", "/token", "/refresh")
// Definisci gli scope richiesti
Scope("api:read", "Accesso in lettura all'API")
Scope("api:write", "Accesso in scrittura all'API")
})
Lo schema di sicurezza sopra stabilisce la configurazione core OAuth2 per la tua API. Nominandolo “oauth2”, crei un identificatore chiaro che può essere referenziato in tutto il design della tua API. Lo schema specifica il tuo flusso OAuth2 supportato, che in questo caso è il flusso Authorization Code - una delle opzioni più sicure disponibili. Definisce anche gli scope disponibili che i client possono richiedere quando accedono alla tua API, permettendo un controllo degli accessi granulare. Infine, configura gli endpoint di autenticazione necessari con cui i client interagiranno durante il flusso OAuth2, inclusi gli endpoint di autorizzazione, scambio token e refresh token. Questa configurazione completa fornisce tutto il necessario per implementare la sicurezza OAuth2 nella tua API Goa.
Ecco come applicare la sicurezza OAuth2 ai tuoi endpoint API:
var _ = Service("secure_api", func() {
Description("API protetta da OAuth2")
Method("getData", func() {
Description("Ottieni dati protetti")
// Richiedi OAuth2 con scope specifico
Security(OAuth2Auth, func() {
Scope("api:read")
})
Payload(func() {
AccessToken("token", String, "Token di accesso OAuth2")
Required("token")
// Campi payload aggiuntivi
Field(1, "query", String, "Query di ricerca")
})
Result(ArrayOf(String))
HTTP(func() {
GET("/data")
Param("query")
Response(StatusOK)
Response(StatusUnauthorized)
})
})
})
Quando Goa genera il codice, dovrai implementare un gestore di sicurezza:
func (s *service) OAuth2Auth(ctx context.Context, token string, scheme *security.OAuth2Scheme) (context.Context, error) {
// Valida il token con il tuo provider OAuth2
tokenInfo, err := s.oauth2Provider.ValidateToken(token)
if err != nil {
return ctx, err
}
// Verifica gli scope
if err := validateScopes(tokenInfo.Scopes, scheme.RequiredScopes); err != nil {
return ctx, err
}
// Aggiungi informazioni utente al contesto
ctx = context.WithValue(ctx, "user_id", tokenInfo.UserID)
ctx = context.WithValue(ctx, "scopes", tokenInfo.Scopes)
return ctx, nil
}
Implementa una gestione sicura dei token:
func validateToken(token string) (*TokenInfo, error) {
// Verifica la firma del token
if !isValidSignature(token) {
return nil, errors.New("firma token non valida")
}
// Verifica la scadenza
if isExpired(token) {
return nil, errors.New("token scaduto")
}
// Estrai e valida le informazioni del token
return extractTokenInfo(token)
}
Gestisci gli errori OAuth2 in modo appropriato:
func handleOAuth2Error(err error) error {
switch {
case errors.Is(err, ErrInvalidToken):
return genservice.MakeUnauthorized(fmt.Errorf("token non valido"))
case errors.Is(err, ErrInsufficientScope):
return genservice.MakeForbidden(fmt.Errorf("scope insufficiente"))
case errors.Is(err, ErrExpiredToken):
return genservice.MakeUnauthorized(fmt.Errorf("token scaduto"))
default:
return genservice.MakeInternalError(err)
}
}
Implementa il rate limiting per prevenire abusi:
func (s *service) checkRateLimit(token string) error {
key := fmt.Sprintf("ratelimit:%s", token)
count, err := s.redis.Incr(key)
if err != nil {
return err
}
// Imposta limite di 1000 richieste per ora
if count == 1 {
s.redis.Expire(key, time.Hour)
}
if count > 1000 {
return ErrRateLimitExceeded
}
return nil
}
Ecco un esempio completo che mostra come implementare l’autenticazione OAuth2 in un servizio Goa:
package design
import (
. "goa.design/goa/v3/dsl"
)
var OAuth2Auth = OAuth2Security("oauth2", func() {
Description("Autenticazione OAuth2")
// Configura il flusso authorization code
AuthorizationCodeFlow("/auth", "/token", "/refresh")
// Definisci gli scope disponibili
Scope("api:read", "Accesso in lettura")
Scope("api:write", "Accesso in scrittura")
})
var _ = API("auth_api", func() {
Title("API Autenticata")
Description("API che dimostra l'autenticazione OAuth2")
// Applica OAuth2 di default
Security(OAuth2Auth)
})
var _ = Service("documents", func() {
Description("Servizio gestione documenti")
Method("list", func() {
Description("Lista documenti")
Security(OAuth2Auth, func() {
Scope("api:read")
})
Result(ArrayOf(Document))
HTTP(func() {
GET("/documents")
Response(StatusOK)
Response(StatusUnauthorized)
})
})
Method("create", func() {
Description("Crea documento")
Security(OAuth2Auth, func() {
Scope("api:write")
})
Payload(Document)
Result(Document)
HTTP(func() {
POST("/documents")
Response(StatusCreated)
Response(StatusUnauthorized)
})
})
})