Basic Authentication
- 기본 인증의 정의, 동작 원리
- RFC 7617 표준에 따라 기본 인증이 어떻게 정의되는가?
- Authorization 헤더의 구조, 예
- 기본 인증이 동작하는 전체 과정을 단계별로 이해하기
- 기본 인증의 장점, 한계
기본 인증(Basic Authentication)
1. RFC 7617
기본 인증이 HTTP 표준 인증 방식중 하나로, RFC 7617에 정의되어 있음
사용자 이름과 비밀번호를 콜론(:, 세미콜론 x)으로 연결한 뒤, Base64로 인코딩하여 Authorization 헤더에 담아서 서버에 전달함
매우 단순한 구조 덕에 구현이 쉽지만, 보안적으로 취약한점이 있다고 함
(1) RFC 7617에 대한 기초
RFC 7617은 HTTP Basic Authentication Scheme에 대한 기술 표준 문서이다.
https://datatracker.ietf.org/doc/html/rfc7617
- 정의 : 사용자의 ID와 비밀번호를 결합해서 서버에 전송하는 가장 기본적 인증 방식인 Basic 인증을 정의함
- 동작방식
- 아이디:비밀번호 형태의 문자열을 만듬
- 이 문자열을 Base64로 인코딩함
- HTTP 요청 헤더에 Authorization: Basic[...=] 이런 형태로 실어서 보냄
- 특징
- UTF-8을 지원함. 아이디/비밀번호에 비-ASCII 문자(한글 등)가 포함될 때 처리방식이 애매했는데, UTF-8을 사용하도록 명시하고 인코딩 처리 방식을 개선했음
- 주의사항
- Base64는 암호화가 아닌 인코딩이기 때문에 디코딩 될 수 있으므로 반드시 HTTPS(SSL/TLS) 환경에서 사용해야 안전하다
2. 개념
클라이언트 : 사용자 이름:비밀번호 - > username:password
인코딩 : Base64로 인코딩함
서버 요청 : 인코딩한 문자열을 Authorization 헤더에 담아서 전송함
GET /mypage HTTP/1.1
Host: example.com
Authorization: Basic ...=
Authorization 헤더 확인 -> Basic 접두사 제거 후 Base64 디코딩 -> username:password 추출
-> 서버 저장소의 자격 증명과 비교한 뒤 인증 성공 여부를 결정함
Authorization 헤더
1. 구조 설명
| 구성 요소 | 예시 | 설명 |
| 인증방식 | Basic | RFC 7617에서 정의한 키워드 |
| 인코딩된 자격 증명 | ...= | username:password를 Base64로 인코딩한 값 |
최종 형태
Authorization: Basic <Base64로 인코딩된 문자열=>
2. 동작 흐름 다이어그램
기본 인증에서 Authorization 헤더는 클라이언트가 서버에 자신의 자격 증명을 전달하는 핵심 역할을 함
- 사용자가 보호된 리소스에 접근을 시도함
- 서버는 401 Unauthorized와 함께 WWW-Authenticate: Basic realm="..." 헤더를 응답하여 인증을 요구함
- 브라우저(클라이언트)는 사용자에게 아이디와 비밀번호 입력을 요청함
- 입력받은 아이디와 비밀번호를 username:password 형태로 합친 후, Base64로 인코딩함
- 인코딩된 문자열을 Authorization: Basic <값> 형태로 요청 헤더에 담아서 서버에 다시 보낸다(브라우저->서버)
- 서버는 Base64 디코딩 후 사용자의 저장소와 비교하여 인증 성공 여부를 판단한다
기본 인증 구현(러프하게)
User.java
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
UserRepository.java
public class UserRepository {
public boolean validate(String username, String password) {
return "user".equals(username) && "password".equals(password);
}
}
BasicAuthHandler.java
import java.util.Base64;
public class BasicAuthHandler {
private final UserRepository userRepository;
public BasicAuthHandler(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 요청 헤더에서 Authorization을 받아서 검증함
public boolean authenticate(String authHeader) {
if(authHeader == null || !authHeader.startsWith("Basic ")) {
return false; // 인증 헤더가 없음
}
// Basic 접두사 제거하기
String base64Credentials = authHeader.substring("Basic ".length());
// Base64 디코딩하기
byte[] decodeByte = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(decodeByte);
// 아이디와 비밀번호 분리하기
String[] parts = credentials.split(":", 2);
if(parts.length != 2) { // 검증로직, username:password 형태가 아니라 a:b:c 등이라면
return false; // 분리가 잘못됐거나 뭔가 이상함
}
String username = parts[0];
String password = parts[1];
// 저장소에서 사용자를 검증함
return userRepository.validate(username, password);
}
}
BasicAuthHandlerTest.java
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class BasicAuthHandlerTest {
private BasicAuthHandler authHandler;
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
authHandler = new BasicAuthHandler(userRepository);
}
@Test
void authenticate_success() {
// Given
String username = "B1uffer";
String password = "password123";
String credentials = username + ":" + password;
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
String authHeader = "Basic " + encodedCredentials;
System.out.println("credentials : " + credentials);
System.out.println("authHeader : " + authHeader);
when(userRepository.validate(username, password)).thenReturn(true);
// when
boolean result = authHandler.authenticate(authHeader);
System.out.println("result : " + result);
// then
assertTrue(result);
verify(userRepository).validate(username, password);
}
@Test
void authenticate_fail_password() {
//Given
String username = "B1uffer";
String password = "password123";
String credentials = username + ":" + password;
String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
when(userRepository.validate("B1uffer", "wrongpassword")).thenReturn(false);
System.out.println("credentials : " + credentials);
// when
boolean result = authHandler.authenticate(authHeader);
System.out.println("result : " + result);
// then
assertFalse(result);
}
@Test
void authenticate_fail_header() {
// 필터링에 대해서 테스트함
assertFalse(authHandler.authenticate(null));
assertFalse(authHandler.authenticate("Bearer anyToken"));
}
@Test
void authenticate_fail_credentials() {
// Base64의 형식이 맞지 않는 경우, : 빠져있음
String authHeader = "Basic " + Base64.getEncoder().encodeToString("B1ufferPassword123".getBytes());
when(userRepository.validate("B1uffer", "password123")).thenReturn(false);
// when
boolean result = authHandler.authenticate(authHeader);
// then
assertFalse(result);
}
}
기본 인증의 한계와 보안 위험성
- 인코딩은 암호화가 아님 : Base64는 단순 인코딩일 뿐, 보안 기능이 전혀 없다
- 재전송 위험 : 요청마다 자격 증명이 반복적으로 전송되므로 탈취의 위험이 있다
- 저장소 문제 : 서버는 사용자의 비밀번호를 안전하게 저장해야하며(해시, 솔트 등), 그렇지 않으면 유출의 위험이 있다
- 현대적 대체 수단이 필요함 : 실제 서비스에서는 세션 기반 인증이나 토큰 기반 인증(JWT 등)이 일반적으로 사용됨
정리
| 항목 | 설명 |
| RFC 7617 | 기본 인증을 정의한 공식 표준 |
| Authorization 헤더 | Basic username:password를 Base64로 인코딩한 형태 |
| 장점 | 단순하고, 표준 지원이 넓음 |
| 단점 | 보안에 취약하고(노출 위험), 매 요청시 전송됨 |
'Spring Boot > 유저 관리 기능' 카테고리의 다른 글
| 기본 인증, 인코딩 : 기본 인증 보안 고려사항 (1) | 2026.03.10 |
|---|---|
| 기본 인증, 인코딩 : 인코딩과 Base64URL (0) | 2026.03.10 |
| 쿠키와 세션 기반 인증 : 쿠키와 세션 보안 설정 (0) | 2026.03.08 |
| 쿠키와 세션 기반 인증 : 세션 기반 인증 (0) | 2026.03.07 |
| 쿠키와 세션 기반 인증 : 쿠키의 개념과 특성 (0) | 2026.03.05 |