JWT (JSON Web Token) é padrão (RFC 7519) de token stateless (geralmente do tipo Bearer token) usado para autenticação/autorização na troca de informações entre cliente e servidor (HTTP request/response) de forma segura (geralmente em conjunto com protocolo OAuth2), assinadas com Message Authentication Code (HMAC). Access Token é token de curta duração com expiração, usado para acessar recursos protegidos da API, enviado em cada request no header Authorization. Refresh Token é token de longa duração de expiração, usado apenas para gerar novos access tokens. Sua estrutura consiste em header (tipo de token e algoritmo de assinatura), payload (dados claims, como subject, issuedAt, expiration) e signature (assinatura criptográfica do header e payload usando secret key). JWT amplamente utilizado em sistemas web RESTful (arquitetura REST) stateless (sem manter estado no servidor), com funções encoder (criação) e decoder (leitura).
Request de usuário não autenticado será rejeitada (401 Unauthorized). Primeiramente, usuário regitra suas credenciais (/api/auth/register), depois acessa (/api/auth/login) para obter access token, para então acessar endpoints protegidos. Usuário pode gerar refresh token (/api/auth/refresh) quando o access token expirar. Por fim, usuário pode fazer logout (/api/auth/logout) com exclusão do refresh token.
Cliente registra suas credenciais (/api/auth/register), acessa (/api/auth/login), recebe tokens, depois envia access token na request protegida (/api/hello). Filtro valida token, liberando acesso ao controller. Nesse exemplo simples, refresh token é gerado apenas para demonstração (ideal é gerar em /api/auth/refresh).
Projeto Spring Boot Kotlin API REST para implementação de JWT. Nesse exemplo simples, refresh token é gerado apenas para demonstração (ideal é gerar em /api/auth/refresh).
com.example.myjwt.controller
com.example.myjwt.dto
com.example.myjwt.security
com.example.myjwt.service
plugins {
kotlin("jvm") version "2.2.21"
kotlin("plugin.spring") version "2.2.21"
id("org.springframework.boot") version "4.0.6"
id("io.spring.dependency-management") version "1.1.7"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springframework.boot:spring-boot-starter-security")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
package com.example.myjwt.dto
data class HelloResponse(
val message: String,
val status: String
)
package com.example.myjwt.controller
import com.example.myjwt.dto.HelloResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class HelloController {
@GetMapping("/hello")
fun hello(): HelloResponse {
return HelloResponse(
message = "API REST funcionando!",
status = "OK"
)
}
}
'//implementation("org.springframework.boot:spring-boot-starter-security")'
'implementation("org.springframework.boot:spring-boot-starter-security")'
package com.example.myjwt.security
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Service
import java.util.*
import javax.crypto.SecretKey
@Service
class JwtService {
private val secretKey: SecretKey = Keys.hmacShaKeyFor(
"my-super-secret-key-my-super-secret-key".toByteArray()
)
private val accessExpirationMs = 1000 * 60 * 5L
private val refreshExpirationMs = 1000 * 60 * 60 * 24L
fun generateAccessToken(username: String): String {
return Jwts.builder()
.subject(username)
.issuedAt(Date())
.expiration(Date(System.currentTimeMillis() + accessExpirationMs))
.signWith(secretKey)
.compact()
}
fun generateRefreshToken(username: String): String {
return Jwts.builder()
.subject(username)
.issuedAt(Date())
.expiration(Date(System.currentTimeMillis() + refreshExpirationMs))
.claim("type", "refresh")
.signWith(secretKey)
.compact()
}
fun extractUsername(token: String): String {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.payload
.subject
}
}
package com.example.myjwt.security
import jakarta.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
@Configuration
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() }
.formLogin { it.disable() }
.httpBasic { it.disable() }
.exceptionHandling {
it.authenticationEntryPoint { _, response, _ ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
}
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.authorizeHttpRequests {
it
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(
jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter::class.java
)
return http.build()
}
}
package com.example.myjwt.security
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.User
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
@Component
class JwtAuthenticationFilter(
private val jwtService: JwtService
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val authHeader = request.getHeader("Authorization")
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response)
return
}
val token = authHeader.substring(7)
try {
val username = jwtService.extractUsername(token)
val auth = UsernamePasswordAuthenticationToken(
User(username, "", emptyList()),
null,
emptyList()
)
SecurityContextHolder.getContext().authentication = auth
} catch (e: Exception) {
SecurityContextHolder.clearContext()
}
filterChain.doFilter(request, response)
}
}
package com.example.myjwt.dto
data class RegisterRequest(
val username: String,
val password: String
)
package com.example.myjwt.dto
data class LoginRequest(
val username: String,
val password: String
)
package com.example.myjwt.dto
data class TokenResponse(
val accessToken: String,
val refreshToken: String
)
package com.example.myjwt.service
import org.springframework.stereotype.Service
@Service
class UserService {
private val users = mutableMapOf<String, String>()
fun register(username: String, password: String) {
if (users.containsKey(username)) {
throw RuntimeException("User already exists")
}
users[username] = password
}
fun validate(username: String, password: String): Boolean {
return users[username] == password
}
}
package com.example.myjwt.controller
import com.example.myjwt.dto.LoginRequest
import com.example.myjwt.dto.RegisterRequest
import com.example.myjwt.dto.TokenResponse
import com.example.myjwt.security.JwtService
import com.example.myjwt.service.UserService
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/auth")
class AuthController(
private val jwtService: JwtService,
private val userService: UserService
) {
@PostMapping("/register")
fun register(@RequestBody request: RegisterRequest): String {
userService.register(request.username, request.password)
return "User registered successfully"
}
@PostMapping("/login")
fun login(@RequestBody request: LoginRequest): TokenResponse {
val isValid = userService.validate(request.username, request.password)
if (!isValid) {
throw RuntimeException("Invalid credentials")
}
val accessToken = jwtService.generateAccessToken(request.username)
val refreshToken = jwtService.generateRefreshToken(request.username)
return TokenResponse(accessToken, refreshToken)
}
}
{
"username": "admin",
"password": "123"
}
{
"username": "admin",
"password": "123"
}
Elaborado por Mateus Schwede
ubsocial.github.io