| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- ZOOM
- 서버리스 #
- 컴포넌튼
- 당근마켓
- angular
- npm
- literal
- &연산
- 1px border
- 데이터베이스 #try #이중
- TS
- 0.25px border
- 으
- 클론코딩
- 타입스크립트
- 0.75px border
- Strict
- ES5
- Websocket
- font-size
- 0.5px border
- entity
- 전역변수
- TypeScript
- Props
- 10px
- github
- es6
- jwt
- 문서번호
Archives
- Today
- Total
복잡한뇌구조마냥
[Spring] Spring Security + JWT 인증 구조 본문
1. 스프링 시큐리티란?
- 스프링 기반 애플리케이션의 인증(Authentication) & 인가(Authorization) 프레임워크
- 필터 기반 아키텍처로 동작
- 내부적으로 SecurityFilterChain을 기반으로 요청을 처리함
- 특징:
- 필터 기반 구조 (서블릿 컨테이너 레벨에서 동작)
- 설정 기반 보안 (configure → bean 등록)
- 확장성이 뛰어남 (커스텀 필터 추가 가능)
- OAuth2, JWT, Form Login, Session 기반 등 다양하게 지원
2. 인증 vs 인가 개념 정리
✔ 인증(Authentication)
"누구인지 확인하는 단계"
- 로그인 시도 → 토큰/JWT/세션 → 본인임을 증명
✔ 인가(Authorization)
"권한이 있는지 확인하는 단계"
- ex) 어떤 게시글의 수정 버튼을 누를 때 → ROLE_USER가 가능한지 체크
👉 요약 그림
[사용자 정보 확인] = 인증
[해당 자원이 가능한 권한인지 체크] = 인가
3. 스프링 시큐리티 요청 흐름 (JWT 기준)

요청 흐름 전체:
- 사용자가 로그인 요청을 보냄
- 커스텀 필터(CustomAuthenticationFilter)가 동작
→ MemberService로 DB 조회
→ 비밀번호 비교
→ 성공 시 JWT 발급 - 이후 클라이언트는 Authorization 헤더에 JWT 포함
→ Bearer xxx.yyy.zzz - JwtAuthenticationFilter가 요청마다 헤더의 JWT 검증 수행
→ 사용자 정보(SecurityContextHolder)에 저장 - 컨트롤러 @PreAuthorize / 권한 체크
4. SecurityConfig — 프로젝트의 보안 정책을 모두 정의하는 핵심 클래스
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final CustomUserDetailService userDetailService;
private final CustomAuthenticationFilter customAuthenticationFilter;
private final CustomOAuth2UserService customOAuth2UserService;
private final OAuth2SuccessHandler oAuth2SuccessHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/api/v1/auth/login").permitAll()
.requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
.successHandler(oAuth2SuccessHandler)
)
.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
🔍 핵심 포인트 정리
1) 세션 비활성화
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
JWT 사용 시 세션 방식이 아니므로 반드시 STATELESS로 지정해야 한다.
2) CORS 설정 별도 적용
리액트/웹앱에서 요청 시 반드시 필요.
3) 허용 URL 정의
로그인 엔드포인트(/api/v1/auth/login)는 permitAll
나머지는 인증 필요.
4) 커스텀 로그인 필터 등록
.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
스프링 기본 UsernamePasswordAuthenticationFilter 이전 단계에서
CustomAuthenticationFilter를 실행하도록 설정한다.
5. CustomAuthenticationFilter — 로그인 요청을 가로채는 핵심 역할
이 필터는 사용자의 로그인 요청을 처리하고, 인증 성공 시 JWT 발급을 담당한다.
public class CustomAuthenticationFilter extends OncePerRequestFilter {
private final MemberService memberService;
private final JwtProvider jwtProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 로그인 요청만 처리
if (!isLoginRequest(request)) {
chain.doFilter(request, response);
return;
}
LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);
SecurityUser securityUser = memberService.authenticateUser(loginRequest);
// 인증 성공 → JWT 발급
String accessToken = jwtProvider.generateAccessToken(securityUser);
writeResponse(response, accessToken);
}
}
🧩 CustomAuthenticationFilter 핵심 역할
| 기능 | 설명 |
| 로그인 요청 감지 | /api/v1/auth/login 같은 특정 경로만 필터링 |
| 요청 바디 파싱 | JSON → LoginRequest |
| 사용자 인증 처리 | MemberService를 통해 email/pw 검증 |
| JWT 발급 | JwtProvider.generateAccessToken() 호출 |
| 응답 생성 | 토큰 반환 |
특히 이 필터는 스프링 시큐리티의 기본 인증 흐름을 아예 커스텀으로 대체하는 방식이므로, 실무에서 많이 사용되는 패턴이다.
6. CustomUserDetailService — DB에서 회원 정보를 조회하는 인증 로직
@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String email) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("회원 정보를 찾을 수 없습니다."));
return new SecurityUser(member);
}
}
⭐ 역할 요약
- 스프링 시큐리티가 인증 시 호출
- DB에서 email 기반 사용자 조회(아이디를 사용하면 username 사용)
- 조회된 엔티티를 SecurityUser 형태로 래핑하여 반환
이 메서드가 반환하는 UserDetails 구현체가 AuthenticationManager 내부에서 사용된다.
7. SecurityUser — 인증된 사용자를 시큐리티 컨텍스트에 올리는 객체
@Getter
public class SecurityUser implements UserDetails {
private final Long id;
private final String email;
private final String nickname;
private final Collection<? extends GrantedAuthority> authorities;
public SecurityUser(Member member) {
this.id = member.getId();
this.email = member.getEmail();
this.nickname = member.getNickname();
this.authorities = List.of(new SimpleGrantedAuthority(member.getRole().name()));
}
@Override public String getPassword() { return null; }
@Override public String getUsername() { return email; }
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return true; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return true; }
}
📝 정리
SecurityUser는 인증이 완료된 사용자 정보를 시큐리티 컨텍스트(SecurityContextHolder)에 저장하는 역할을 한다.
여기서 비밀번호는 JWT 방식이므로 인증 이후에는 필요 없기 때문에 null 처리했다.
8. 전체 JWT 인증 흐름 정리

- 전체 플로우 요약
- 클라이언트 → /api/v1/auth/login POST 요청
- CustomAuthenticationFilter가 요청 바디 파싱
- UserDetailService에서 사용자 조회
- 비밀번호 검증
- JWT 발급
- 응답으로 토큰 반환
- 이후 Authorization 헤더 기반 인증
- JwtAuthenticationFilter에서 매 요청마다 토큰 검증
- SecurityContextHolder에 SecurityUser 저장
- 컨트롤러 진입 → @AuthenticationPrincipal로 사용자 정보 접근 가능
🏁 마무리
정리한 내용은 실제 서비스에서 가장 많이 사용하는 방식인 Spring Security + JWT 기반 인증 흐름 구조입니다.
SecurityFilterChain부터 CustomAuthenticationFilter까지 전체적인 흐름이 이해되면,
확장(Refresh Token / 권한 구분 / Role 기반 접근 제어)도 훨씬 쉬워집니다.
LIST
'BE > Spring' 카테고리의 다른 글
| [Spring] Spring Security + OAuth2 로그인 (0) | 2025.10.15 |
|---|---|
| [Spring] JPA + QueryDSL 정리 (0) | 2025.10.02 |
| [Spring] MyBatis 구조 정리 (0) | 2025.08.29 |
| [Spring] 스프링 부트 생성 (1) | 2025.06.18 |
| [Spring + JPA] 인프런 김영한님 커리큘럼 (2) | 2025.06.18 |