Autenticazione via token JWT con Angular e Spring Boot: parte 3, generazione del token JWT

(Questo è il quarto post di una serie che inizia qui e prosegue qui e qui)

Nelle puntate precedenti:

  • Abbiamo descritto come funziona il flusso dell’autenticazione
  • Abbiamo visto l’implementazione del Google login nell’applicazione Angular
  • Abbiamo visto come configurare Spring Boot per gestire l’autenticazione

Ci resta solo da vedere come è effettivamente implementato l’endpoint /authenticate, che valida l’utente e gli restituisce un token JWT.

Il servizio invocato dal controller è fatto così:

@Service
class JwtAuthenticationService(
    private val userService: UserService,
    private val googleIdTokenVerifierService: GoogleIdTokenVerifierService,
    private val jwtTokenService: JwtTokenService
) {

    fun authenticate(request: JwtAuthenticationRequest): JwtAuthenticationResponse {
        userService.findByEmail(request.username)
            ?: throw UsernameNotFoundException("User with email $request.username not found.")

        googleIdTokenVerifierService.verifyToken(request.googleToken)

        val jwtToken = jwtTokenService.generateToken(request.username)
        return JwtAuthenticationResponse(jwtToken, "")
    }
}

Non fa niente di complicato, in effetti, fa solo tre cose:

  • Valida lo username sul DB chiedendo allo userService se esiste effettivamente un utente con quello username
  • Valida il token ritornato al frontend dall’autenticazione di Google
  • Genera un token JWT e lo restituisce

La validazione su DB è banale, per cui confido che non serva dilungarcisi ulteriormente; anche quella del token di Google non è particolarmente complicata, grazie alla dipendenza da com.google.api-client:google-api-client che mette a disposizione la classe GoogleIdTokenVerifier.

@Service
class GoogleIdTokenVerifierService {

    @Value("\${security.oauth2.client.clientId}")
    val clientId: String = ""

    fun verifyToken(token: String): GoogleIdToken {
        val verifier = GoogleIdTokenVerifier
                .Builder(NetHttpTransport(), JacksonFactory.getDefaultInstance())
                .setAudience(listOf(clientId))
                .build()

        return verifier.verify(token) ?: throw IllegalArgumentException()
    }
}

A questo punto, ci resta solo da generare il token JWT:

fun generateToken(username: String): String {
    return Jwts.builder()
        .setClaims(mutableMapOf())
        .setSubject(username)
        .setIssuedAt(Date())
        .setExpiration(Date(System.currentTimeMillis() + (jwtValidity * 10000)))
        .signWith(SignatureAlgorithm.HS256, jwtSecret)
        .compact()
}

E il gioco è fatto: come abbiamo visto nelle puntate precedenti, il frontend passerà il token così generato e appena ottenuto nell’header Authorization di ogni richiesta al backend, permettendo quindi di autenticarsi.

La parte più critica in tutto questo cinema, l’avrete intuito, è la configurazione dell’autenticazione di Spring Boot, che ha una serie di gotcha e momenti AHA! niente affatto banali che mi hanno fatto sbattere la testa ripetutamente contro il muro e mi hanno spinto a scrivere questa serie di post, nella speranza di aiutare i poveri sventurati che si troveranno nella mia stessa situazione.

Come si suol dire, il modo migliore per imparare qualcosa è spiegarlo a qualcun’altro.