자바 백엔드 입문 37편. Spring Security가 무엇이고 SecurityFilterChain·인증·인가·JWT 기초 패턴을 회사 출입증 시스템 비유로 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 37편이에요. 백엔드의 가장 큰 영역 — Spring Security를 입문 수준에서 풀어 가요. 이 한 글로 다 다루긴 어렵지만, "전체 그림" 잡는 게 목표.
Spring Security가 어렵게 들리는 이유
처음 Spring Security 의존성 추가하면 — 모든 API가 갑자기 401. 무엇이 어디서 막혔는지 안 잡혀요. 또 SecurityFilterChain·UserDetailsService·AuthenticationManager·PasswordEncoder 같은 단어가 한꺼번에.
이 글에서는 회사 출입증 시스템 비유로 풀어요. Spring Security = "사옥 정문 + 출입카드 발급 + 부서별 출입 통제 시스템". 끝까지 따라오시면 입문자 수준에서 전체 그림이 잡혀요.
인증 vs 인가 — 두 개념 분리
| 개념 | 영어 | 한 줄 |
|---|---|---|
| 인증 (Authentication) | 누구인지 확인 | 로그인 — "당신은 alice인가?" |
| 인가 (Authorization) | 무엇을 할 수 있는지 확인 | 권한 — "alice가 관리자 페이지 들어올 수 있나?" |
회사 사옥 비유 — 인증 = 출입증 발급("당신 신원 확인 완료"), 인인 = 부서별 출입 통제("개발자라 회의실 OK, 임원실 X"). 둘은 다른 단계.
Spring Security 의존성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
}
이 한 줄 추가하면 — 모든 API가 자동 보호. 기본 ID·비밀번호로만 접근 가능 (콘솔에 임시 비밀번호 출력).
이 "기본 동작" 을 우리 정책에 맞게 커스터마이즈하는 게 Spring Security 사용의 핵심.
SecurityFilterChain — 핵심 부품
Spring Security는 26편 Filter 의 연쇄로 동작. 십수 개의 Filter가 차례로 요청을 검사해요.
[요청]
↓
[CsrfFilter]
↓
[CorsFilter]
↓
[UsernamePasswordAuthenticationFilter] ← 로그인 처리
↓
[AuthorizationFilter] ← 권한 검사
↓
[DispatcherServlet]
↓
[컨트롤러]
이 필터들의 체인 설정이 SecurityFilterChain Bean.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 비활성 (REST API)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll() // 공개 경로
.requestMatchers("/api/admin/**").hasRole("ADMIN") // 관리자만
.anyRequest().authenticated()) // 나머지 인증 필요
.formLogin(form -> form.disable()) // form 로그인 끄기
.httpBasic(basic -> basic.disable()) // basic 인증 끄기
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
REST API에 자주 박는 표준 골격. CSRF·form·basic 끄고 STATELESS(세션 안 만듦) — JWT 같은 토큰 인증 가정.
권한 검사 — @PreAuthorize
컨트롤러 메서드 단위 권한 검사. 22편 SpEL 표현식 활용.
@EnableMethodSecurity // 메인 클래스 또는 SecurityConfig
public class SecurityConfig { ... }
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
@PreAuthorize("hasRole('USER')") // USER 권한 필요
public Order get(@PathVariable Long id) { ... }
@DeleteMapping("/orders/{id}")
@PreAuthorize("hasRole('ADMIN')") // ADMIN만
public void delete(@PathVariable Long id) { ... }
@PatchMapping("/orders/{id}")
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public Order update(@PathVariable Long id, ...) { ... }
}
@PreAuthorize 가 23편 AOP 기반. 메서드 호출 직전 권한 검사.
PasswordEncoder — 비밀번호 해싱
비밀번호는 절대 평문 저장 X. Spring Security 표준 = BCrypt.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Service
@RequiredArgsConstructor
public class UserService {
private final PasswordEncoder encoder;
public User signup(String email, String rawPassword) {
String hashed = encoder.encode(rawPassword); // BCrypt 해싱
return userRepo.save(new User(email, hashed));
}
public boolean checkPassword(User user, String rawPassword) {
return encoder.matches(rawPassword, user.getPassword());
}
}
BCrypt는 매번 다른 salt 생성 — 같은 비밀번호도 매번 다른 해시. 현대 표준.
JWT — Stateless 토큰 인증
세션 대신 JWT(JSON Web Token) 사용이 REST API 표준.
[로그인 흐름]
1. POST /login {email, password}
2. 서버: 비밀번호 검증 → JWT 생성 → 응답
3. 클라이언트: JWT를 localStorage·쿠키 저장
[이후 API 호출]
4. GET /api/orders, Header: Authorization: Bearer <JWT>
5. 서버: JWT 검증 → 사용자 추출 → API 처리
JWT 구조 — header.payload.signature 세 부분. payload에 사용자 ID·만료시간 등 박힘. 서명으로 위변조 차단.
JWT 처리는 — 별도 Filter 로 구현. SecurityFilterChain 안에 끼워 넣음. 라이브러리는 jjwt (io.jsonwebtoken:jjwt-api) 표준.
@Component
public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
String token = req.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
Claims claims = jwtParser.parseClaimsJws(token.substring(7)).getBody();
String email = claims.getSubject();
// SecurityContext에 인증 정보 박기
UserDetails user = userDetailsService.loadUserByUsername(email);
Authentication auth = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception ignore) { /* 토큰 무효 — 인증 안 됨 */ }
}
chain.doFilter(req, res);
}
}
SecurityConfig에 박기.
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
SecurityContextHolder — 현재 사용자
인증 끝나면 — SecurityContextHolder 에 현재 사용자 박힘. 어디서나 꺼내 쓸 수 있어요.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
Collection<? extends GrantedAuthority> roles = auth.getAuthorities();
30편 ArgumentResolver 의 @LoginUser 또는 Spring Security 내장 @AuthenticationPrincipal 로 자동 주입 가능.
입문자가 알아야 할 것 — 큰 그림만
Spring Security는 매우 깊은 주제예요. 입문에서 잡아야 할 것:
- 의존성 추가 = 자동 보호 활성
- SecurityFilterChain Bean으로 정책 박기
- 인증 vs 인가 구분
- PasswordEncoder 표준 = BCrypt
- REST API 표준 = JWT + Stateless
@PreAuthorize메서드 단위 권한SecurityContextHolder로 현재 사용자
깊은 학습은 별도 "Spring Security 시리즈" 로 따로. 이 시리즈에서는 "있다" 정도.
자체 인증 + JWT 다음 단계 = 카카오·구글·네이버 소셜 로그인. Spring Security OAuth2 Client 로 표준 처리. 회사 시스템 거의 표준이지만 이 시리즈 범위 밖.
한 줄 정리 — Spring Security = SecurityFilterChain 기반 인증·인가. REST API 표준 = JWT + Stateless. 비밀번호 = BCrypt 해싱. 권한 = @PreAuthorize 메서드 단위.
시험 직전 한 번 더 — Spring Security 입문자가 매번 헷갈리는 것
- 인증(Authentication) = 누구인지 확인 (로그인)
- 인가(Authorization) = 무엇을 할 수 있는지 (권한)
spring-boot-starter-security추가 = 모든 API 자동 보호SecurityFilterChainBean = 정책 박는 핵심@EnableWebSecurity= Spring Security 활성- REST API 표준 =
csrf().disable() + STATELESS authorizeHttpRequests= 경로별 권한 설정permitAll()/authenticated()/hasRole("ADMIN")@PreAuthorize= 메서드 단위 권한 (AOP 기반)@EnableMethodSecurity필요PasswordEncoder= 비밀번호 해싱 표준- 표준 =
BCryptPasswordEncoder encoder.encode(password)= 해싱,encoder.matches(raw, hashed)= 검증- 비밀번호 평문 저장 절대 X
- JWT = REST API 표준 토큰 인증
- 구조 =
header.payload.signature Authorization: Bearer <JWT>헤더로 전송- 라이브러리 =
jjwt SecurityContextHolder= 현재 사용자 저장소OncePerRequestFilter= JWT 검증 Filter 패턴@AuthenticationPrincipal= 현재 사용자 자동 주입- 소셜 로그인 = Spring Security OAuth2 Client
- 깊은 학습은 별도 시리즈 필요 — 이 글은 큰 그림만
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 32편 — CORS 설정
- 33편 — @ExceptionHandler @ControllerAdvice
- 34편 — Bean Validation @Valid @NotNull
- 35편 — 커스텀 Validator 만들기
- 36편 — Logback SLF4J 로깅
다음 글: