생체 인증으로 로그인Spring Security 설정 하기

궁금하면 500원·2025년 10월 15일

미생의 스프링

목록 보기
43/48

패스워드 없는 인증 도입 가이드

최근 몇 년 동안 보안성과 편의성을 모두 갖춘 새로운 인증 방식이 주목받고 있습니다.
바로 웹 인증 표준과 이를 기반으로 하는 패스키입니다.
이는 기존의 사용자 이름과 비밀번호를 대체하여, 피싱에 강력하고 사용이 훨씬 간편한 인증 경험을 제공합니다.

Spring Security 환경에서 어떻게 이 혁신적인 인증 방식을 도입할 수 있는지 설명합니다.


1. WebAuthn 및 Passkeys의 이해

WebAuthn이란?

Web Authentication API W3CFIDO Alliance가 협력하여 개발한 웹 표준입니다.
이는 웹 애플리케이션 및 서비스에서 공개 키 암호화를 사용하여 사용자를 인증하는 인터페이스를 표준화합니다.

Passkeys란?

Passkey는 WebAuthn 표준을 준수하는 FIDO 자격 증명을 일컫는 사용자 친화적인 용어입니다.
Passkeys는 사용자 장치에 저장되며, 사용자의 생체 인식이나 장치 PIN을 통해 활성화됩니다.

특징Passkeys (WebAuthn)기존 비밀번호 기반 인증
보안성공개 키 암호화 사용, 피싱 및 중간자 공격에 강력비밀번호 유출 위험, 피싱 공격에 취약
편의성생체 인식 또는 PIN으로 자동 로그인 (입력 불필요)길고 복잡한 비밀번호를 기억하고 입력해야 함
이식성iCloud, Google 계정 등을 통해 여러 장치에 동기화 가능각 서비스마다 비밀번호를 별도로 관리해야 함
인증 요소사용자 장치 자체 (Authenticator)사용자가 기억하는 지식 (Knowledge)

WebAuthn의 핵심은 사용자 인증에 필요한 개인 키가 외부로 전송되지 않고 장치 내부에 안전하게 보관된다는 점입니다.


2. Spring Security를 통한 WebAuthn

Spring Security는 WebAuthn 지원을 통합하여 개발자가 비교적 적은 설정으로 패스키 인증을 도입할 수 있도록 돕고 있습니다.

1단계: 의존성 추가

WebAuthn 지원이 포함된 Spring Security 모듈을 프로젝트에 추가합니다.

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-webauthn</artifactId>
    <version>0.0.1-SNAPSHOT</version> </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2단계: Spring Security 설정

기존 SecurityFilterChain 설정에 WebAuthn 지원을 추가합니다.
WebAuthn은 초기 등록을 위해 폼 기반 로그인(Form Login)과 함께 사용될 수 있습니다.

Java Config 예제:

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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;

@Configuration
public class SecurityConfiguration {

    // 1. PasswordEncoder 설정
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 실제 애플리케이션에서는 안전한 인코더를 사용해야 합니다.
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    
    // 2. 초기 사용자 설정 (WebAuthn 등록을 위한 폼 로그인 계정)
    @Bean
    public UserDetailsService userDetailsService() {
        // 실제 애플리케이션에서는 DB 기반의 UserDetailsService를 사용합니다.
        return new InMemoryUserDetailsManager(
            User.withUsername("josh")
                .password(passwordEncoder().encode("pw")) // 실제로는 더 강력한 패스워드를 사용해야 합니다.
                .roles("USER")
                .build()
        );
    }
    
    // 3. SecurityFilterChain 설정
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                // 로그인 페이지는 모두 접근 허용
                .requestMatchers("/login", "/register-passkey").permitAll() 
                // 그 외 모든 요청은 인증 필요
                .anyRequest().authenticated()
            )
            // 폼 로그인 설정 (최초 Passkey 등록을 위해 필요)
            .formLogin(form -> form
                .loginPage("/login")
            )
            // WebAuthn/Passkeys 지원 설정
            .with(new WebAuthnConfigurer(), webauthn -> {
                // Relying Party (RP) 정보 설정
                webauthn.relyingPartyId("localhost") // 현재 도메인
                        .relyingPartyName("Beautiful Passkeys App")
                        .allowedOrigins("http://localhost:8080"); // 허용 오리진
                // 사용자 등록 정보 저장소 및 기타 세부 설정 (실제 구현 시 필요)
                // webauthn.credentialRepository(myCredentialRepository()); 
            });
            
        return http.build();
    }
}

3단계: Passkey 등록 및 인증 플로우

WebAuthn을 도입하면 사용자의 인증 플로우는 다음과 같이 단순화됩니다.

  1. 최초 등록:
    • 사용자는 폼 로그인을 통해 기존 계정으로 로그인합니다.
    • 사용자 프로필 페이지에서 "Passkey 등록"을 요청합니다.
      예: /register-passkey 엔드포인트
    • 브라우저는 OS의 WebAuthn API를 호출하고, 사용자는 생체 인식 센서를 통해 자신의 Passkey를 등록합니다.
    • Spring Security는 이 Passkey에 해당하는 공개 키를 서버에 저장합니다.
  2. 로그인:
    • 사용자가 로그인 페이지에 접근하면, 브라우저는 Passkey를 사용하여 로그인할 것인지 묻습니다.
    • 사용자는 생체 인식을 통해 장치에서 인증합니다.
    • 장치는 개인 키를 사용하여 인증 서명을 생성하고, Spring Security는 저장된 공개 키로 이 서명을 검증합니다.
    • 검증에 성공하면 사용자는 로그인됩니다. 비밀번호 입력 과정이 완전히 생략됩니다.

3. WebAuthn이 가져오는 가치

난독화된 요구사항 해결

WebAuthn은 보안이라는 비기능적 요구사항을 "우아하고, 효율적이며, 빠르게" 해결할 수 있게 합니다. 복잡한 비밀번호 관리, MFA(Multi-Factor Authentication) 구현 부담을 줄이면서도 훨씬 높은 수준의 보안을 달성합니다.

궁극의 편의성 및 보안

  • 비밀번호 해방: 더 이상 복잡한 비밀번호를 기억하거나, 비밀번호 관리자를 사용할 필요가 없습니다.
  • 피싱 방지: 개인 키가 장치에 고정되어 있어, 피싱 사이트가 키를 탈취할 수 없습니다.
  • 간편한 로그인: 지문이나 얼굴 인식 한 번으로 즉시 로그인되어 사용 경험이 크게 향상됩니다.

Passkeys는 Google, Apple, Microsoft 등 주요 IT 기업들이 적극적으로 지원하고 있는 만큼, 가까운 미래에 가장 보편적인 웹 인증 방식으로 자리 잡을 것입니다.
Spring Security의 WebAuthn 통합은 이러한 혁신적인 흐름에 발맞춰 안전하고 미래 지향적인 애플리케이션을 구축할 수 있게 해줍니다.

느낀점

이번 WebAuthn 기반의 인증 도입 과정을 보면서, 개발 생태계가 오랜 숙제였던 보안과 사용자 경험의 상충 관계를 드디어 해소하는 단계에 접어들었다는 깊은 인상을 받았습니다.

개인적으로 가장 고무적인 부분은, 보안 강화가 더 이상 개발 복잡도 증가나 사용자의 불편함을 의미하지 않게 되었다는 점입니다.
지난 수년간 우리는 폼 기반 로그인, HTTP Basic, OAuth/OIDC, 그리고 고통스러운 MFA설정 등 수많은 인증 방식을 경험해 왔습니다.
이 과정에서 개발팀은 복잡한 비밀번호 정책 관리, 해시 및 솔트 처리, 저장소 보안 유지 등 비기능적 요구사항에 막대한 리소스를 투입해야 했고, 사용자들은 끝없이 새로운 비밀번호를 외우거나 분실하는 고충을 겪었습니다.

그러나 WebAuthn은 공개 키 암호화라는 근본적인 기술을 활용하여 이러한 문제를 한 번에 해결합니다.
피싱 공격에 취약한 기존 방식과 달리, 인증 요소가 장치 내부에 안전하게 격리되어 있어 서버나 네트워크 보안 문제를 회피합니다.
더욱이, Spring Security와 같은 프레임워크가 이 복잡한 표준을 간결한 설정 몇 줄로 추상화하여 제공하기 시작했다는 것은, '개발자의 고통을 줄이고 본업에 집중하게 만드는' 스프링 진영의 철학이 이번에도 빛을 발한 결과라고 느껴집니다.

결국, 이번 통합은 기술적으로 매우 우수할 뿐만 아니라, 엔터프라이즈 환경에 적용될 때 개발 효율성과 최종 사용자 만족도를 동시에 끌어올릴 수 있는 근본적인 발전이라는 점에서 매우 긍정적이고 기대가 큽니다.
이는 복잡하고 지루하게 느껴졌던 보안 영역이 이제는 우아하고 필수적인 기본 기능으로 자리매김하고 있음을 보여줍니다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글