0.0 2FA Implementación y configuración - Siuss/gamerly-backend GitHub Wiki

Para implementar la autenticación en dos pasos (2FA) en un proyecto Kotlin con Spring Security, sigue estos pasos sugeridos:


1. Agregar Dependencias

Incluye las dependencias necesarias en tu build.gradle.kts o pom.xml:

// Spring Security
implementation("org.springframework.boot:spring-boot-starter-security")
// Biblioteca para TOTP (Time-based One-Time Password)
implementation("com.google.zxing:javase:3.4.1") // Para generar QR codes
implementation("commons-codec:commons-codec:1.15") // Para codificar secretos

2. Extender la Entidad de Usuario

Añade campos para almacenar el secreto 2FA y el estado de habilitación:

@Entity
class User(
    // ... otros campos (email, password, etc.)
    var secret2FA: String? = null, // Secreto para generar códigos TOTP
    var is2FAEnabled: Boolean = false // Estado del 2FA
)

3. Configurar Spring Security

Define un filtro personalizado para manejar la autenticación en dos pasos.

Ejemplo de configuración en SecurityConfig.kt:

@Configuration
@EnableWebSecurity
class SecurityConfig(
    private val userDetailsService: UserDetailsService,
    private val twoFactorAuthenticationFilter: TwoFactorAuthenticationFilter
) : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http
            .authorizeRequests()
                .antMatchers("/login", "/register", "/2fa-setup").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .successHandler(customAuthenticationSuccessHandler())
            .and()
            .addFilterBefore(twoFactorAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
    }

    @Bean
    fun customAuthenticationSuccessHandler(): AuthenticationSuccessHandler {
        return CustomAuthenticationSuccessHandler()
    }
}

4. Generar y Almacenar el Secreto 2FA

Crea un servicio para generar el secreto TOTP y el QR Code:

@Service
class TwoFactorAuthService {

    fun generateSecret(): String {
        val secureRandom = SecureRandom()
        val keyBytes = ByteArray(20)
        secureRandom.nextBytes(keyBytes)
        return Base32().encodeToString(keyBytes)
    }

    fun generateQRCodeUrl(user: User): String {
        return "otpauth://totp/${user.email}?secret=${user.secret2FA}&issuer=MyApp"
    }
}

5. Habilitar 2FA en el Perfil del Usuario

Crea endpoints para manejar la habilitación/deshabilitación del 2FA:

Controlador:

@RestController
@RequestMapping("/2fa")
class TwoFactorAuthController(
    private val twoFactorAuthService: TwoFactorAuthService,
    private val userRepository: UserRepository
) {

    @PostMapping("/enable")
    fun enable2FA(@AuthenticationPrincipal user: User): ResponseEntity<String> {
        val secret = twoFactorAuthService.generateSecret()
        user.secret2FA = secret
        user.is2FAEnabled = true
        userRepository.save(user)
        val qrCodeUrl = twoFactorAuthService.generateQRCodeUrl(user)
        return ResponseEntity.ok(qrCodeUrl)
    }

    @PostMapping("/verify")
    fun verifyCode(@RequestParam code: String, @AuthenticationPrincipal user: User): ResponseEntity<Boolean> {
        val isValid = twoFactorAuthService.validateCode(user.secret2FA!!, code)
        return ResponseEntity.ok(isValid)
    }
}

6. Validar el Código 2FA Durante el Login

Implementa un filtro para verificar el código después de la autenticación inicial:

Filtro Personalizado (TwoFactorAuthenticationFilter.kt):

class TwoFactorAuthenticationFilter(
    private val userDetailsService: UserDetailsService
) : OncePerRequestFilter() {

    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
        if (request.requestURI == "/login" && request.method == "POST") {
            val username = request.getParameter("username")
            val user = userDetailsService.loadUserByUsername(username) as User

            if (user.is2FAEnabled) {
                val code = request.getParameter("code")
                if (!validateCode(user.secret2FA!!, code)) {
                    throw BadCredentialsException("Código 2FA inválido")
                }
            }
        }
        chain.doFilter(request, response)
    }

    private fun validateCode(secret: String, code: String): Boolean {
        val totp = Totp(secret)
        return totp.verify(code)
    }
}

7. Frontend para Configurar 2FA

  • Paso 1: El usuario accede a su perfil y habilita 2FA.
  • Paso 2: Muestra un QR Code generado con generateQRCodeUrl() para que el usuario lo escanee con Google Authenticator o Authy.
  • Paso 3: Solicita al usuario que ingrese un código de verificación para confirmar.

8. Pruebas y Seguridad Adicional

  • Pruebas: Usa apps como Google Authenticator para validar el flujo.
  • Backup Codes: Genera códigos de respaldo en caso de pérdida del dispositivo.
  • Encriptación: Almacena el secreto 2FA encriptado en la base de datos.

Recursos Adicionales

Con estos pasos, nuestra aplicación Gamerly tendrá autenticación en dos pasos robusta y personalizable.


Para garantizar las buenas prácticas

⚠️ **GitHub.com Fallback** ⚠️