백엔드 개발에서 인증과 인가는 거의 필수적으로 등장하는 개념이다.
이번 8주차에서는 Spring Security를 처음 적용하면서 회원가입 API를 구현하고,
Public API / Private API를 분리하는 작업을 진행했습니다.
처음에는 단순히 “로그인 기능을 위한 라이브러리” 정도로 생각했지만, 이번 워크북 개념을 공부하고 미션에서 적용해보니 요청이 Security Filter Chain을 거치며 인증과 인가가 처리되는 구조를 이해하는 과정이 중요하다는 걸 느꼈습니다.
이번 글에서는
를 중심으로 정리해보려고 합니다.
Spring Security는
자바 기반 웹 애플리케이션에서 인증(Authentication)과 인가(Authorization)를 처리하는
보안 프레임워크이다.

Spring 애플리케이션의 요청은 DispatcherServlet에 바로 전달되지 않고,
먼저 Spring Security Filter Chain을 거치게 된다.
이를 통해:
등의 보안 기능을 처리할 수 있다.
인증(Authentication)은 사용자가 누구인지 확인하는 과정이다.
예를 들어:
등이 인증 과정에 해당한다.
Spring Security에서는 사용자가 로그인 요청을 보내면:
UsernamePasswordAuthenticationFilter
AuthenticationManager
UserDetailsService
등을 통해 인증을 수행한다.
이번 미션에서는
를 구현했다.
인가(Authorization)는 인증된 사용자가 특정 리소스에 접근할 권한이 있는지 확인하는 과정이다.
예를 들어:
Spring Security에서는:
.requestMatchers("/api/v1/members/signup").permitAll()
.anyRequest().authenticated()
처럼 접근 권한을 설정할 수 있다.
이번 미션에서는
로 분리하여 인증 여부에 따라 접근을 제어했다.
Stateful 방식은 서버가 사용자 상태(State)를 저장하는 방식이다.
★ 대표적으로 Session 기반 인증 방식이 있다.
사용자가 로그인하면 서버는 세션(Session)을 생성하고 사용자 정보를 저장한다.
이후 클라이언트는 세션 ID를 쿠키에 담아 요청을 보내며, 서버는 세션 정보를 통해 로그인 상태를 확인한다.
특징
이번 미션에서 사용한 formLogin 방식이 Stateful 방식에 해당한다.
Stateless 방식은 서버가 사용자 상태를 저장하지 않는 방식이다.
★ 대표적으로 JWT 기반 인증 방식이 있다.
로그인 이후 서버는 JWT 토큰을 발급하고, 클라이언트는 이후 요청마다 토큰을 함께 전송한다.
서버는 토큰만 검증하여 사용자를 식별한다.
특징
Spring Security는 요청이 들어오면 먼저 Security Filter Chain에서 인증과 인가를 처리한다.
사용자가 로그인 요청을 보내면
인증이 완료되면 Authentication 객체가 생성되고, SecurityContext에 저장된다.
이후 인가 과정에서 사용자의 권한(Role)을 검사하여 접근 가능 여부를 판단한다.
이번 미션에서는 회원가입 API를 구현했다.
회원가입 시:
등의 정보를 저장하도록 구성했다.
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
이메일은 중복되지 않도록 unique 제약을 추가했다.
비밀번호는 BCryptPasswordEncoder를 사용하여 암호화 후 저장했다.
passwordEncoder.encode(request.getPassword())
DB에는 아래와 같은 형태로 저장된다.
2a$10...
이를 통해 비밀번호를 평문으로 저장하지 않도록 처리했다.
@PostMapping("/signup")
public ApiResponse<MemberResDTO.SignupDTO> signup(
@RequestBody MemberReqDTO.SignupDTO request
) {
return ApiResponse.onSuccess(
memberService.signup(request)
);
}
Spring Security에서는 permitAll()과 authenticated()를 사용하여 접근 권한을 설정할 수 있다.
.authorizeHttpRequests(requests -> requests
.requestMatchers(allowUris).permitAll()
.anyRequest().authenticated()
)
Public API : 회원가입 API & Swagger 관련 경로
→ 로그인 없이 접근 가능
Private API : 그 외 API 전부
→ 로그인 필요
초기에는 인증되지 않은 사용자가 Private API에 접근하면 Spring Security 기본 로그인 HTML 페이지가 반환되었다.
하지만 API 서버에서는 HTML 대신 JSON 응답이 필요하기 때문에
AuthenticationEntryPoint
AccessDeniedHandler
를 구현하여 예외 응답을 통일했다.
인증 실패 처리 (401)
{
"isSuccess": false,
"code": "AUTH401",
"message": "로그인이 필요합니다."
}
이번 주차에서 배운 스프링 시큐리티는 실습으로 단순히 회원가입 기능을 구현하는 것보다,
Spring Security가 요청을 어떻게 처리하는지 흐름을 이해하는 과정이 더 중요하게 느껴졌다.
그치만 항상 어렵다
특히
를 직접 적용해보면서 인증과 인가의 차이를 조금 더 명확하게 이해할 수 있었다.
아직은 formLogin 기반의 Stateful 방식만 적용했지만, 이후 JWT 기반 Stateless 인증 구조까지 연결해보면 Spring Security 흐름이 더 명확해질 것 같다.