Security에 대하여
Security란 무엇인가
Spring Security는 스프링에서 제공하는 인증/인가 처리 전용 보안 프레임워크입니다.
주요 역할
- 인증(Authentication): 이 사용자가 누구인가?
-
인가(Authorization): 이 사용자가 이 기능을 사용할 권한이 있는가?
- 로그인 처리 (세션 or 토큰)
- URL, 메서드, 도메인 기반 권한 제어
- 비밀번호 암호화 및 검증
- 세션 관리, 로그아웃, CSRF 방어
- OAuth2 (카카오, 구글 등 소셜 로그인)
- 필터 체인 기반의 보안 처리
Security를 사용하는 이유
security없이 직접 보안을 구현 시
- 사용자 로그인 후, JWT 직접 생성 또는 세션 설정
- 각 API 마다 사용자 인증 체크
- 권한(Role) 체크를 코드 내부에서 수행
- 인증 실패, 권한 없음 등의 직접 예외 처리
if (!"ADMIN".equals(user.getRole())) {
throw new UnauthorizedException();
}
문제점 | 설명 |
---|---|
중복 코드 많음 | 모든 API마다 인증/인가 로직 반복 |
예외 처리 복잡 | 인증 실패, 권한 없음 처리 직접 구현 |
보안 취약 | CSRF, 세션 고정 등 방어 어려움 |
확장성 어려움 | 소셜 로그인, 권한 계층 구조성 등 구현 복잡 |
Security 도입 시
기능 | 설명 |
---|---|
인증/인가 분리 | 로직과 보안 코드 명확히 분리됨 |
보안 필터 자동 구성 | 요청 전 필터에서 인증/인가 처리 |
다양한 로그인 방식 지원 | Form, OAuth2, JWT, 세션 등 |
세밀한 권한 제어 | URL, 메서드 단위까지 제어 가능 |
테스트, 유지보수 용이 | 역할 기반 접근 가능, 단위 테스트도 유리 |
Spring Security 내부 동작 흐름
Request
↓
Security Filter Chain (인증/인가 처리)
↓
DispatcherServlet
↓
Controller (비즈니스 로직)
요청 -> Security Fliter Chain에서 인증/인가 관련 칠터들을 통과 -> 인증 성공 : SecurityContest에 사용자 정보 저장 -> DispatcherServlet을 거쳐 컨트롤러에 도달
구성 요소 | 역할 |
---|---|
UsernamePasswordAuthenticationFilter | 로그인 시 아이디/비밀번호 처리 |
SecurityContext | 인증 정보 저장 공간 |
UserDetailsService | 사용자 정보 로드 서비스 |
AuthenticationManager | 인증 성공 여부 판단 |
AccessDecisionManager | 권한 확인 |
실제 Security 설정 예시
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 전용 URL 설정
.requestMatchers("/login", "/signup").permitAll() // 인증 없이 접근 허용
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
return http.build();
}
}
Security 없이 구현했던 방식
1) 세션 기반 인증
세션: Jaca EE에서 기본 제공. 서버가 세션에 로그인 정보를 저장하고, 클라이언트는 쿠키를 통해 세션 ID를 전달함.
- 로그인 성공 시 HttpSession 객체 생성
- 세션 ID를 쿠키에 담아 브라우저에 저장
- 이후 요청마다 해당 세션 ID로 사용자 식별
@PostMapping("/login")
public String login(HttpServletRequest request, @RequestBody LoginDto dto) {
User user = userRepository.findByUsername(dto.username());
if (user.getPassword().equals(dto.password())) {
request.getSession().setAttribute("loginUser", user.getId());
return "로그인 성공";
}
return "로그인 실패";
}
단점
- 서버가 Session을 유지해야 함
- 클러스터 환경에서 세션 공유 필요
- 세션 고정, 탈취 등 보안 이슈
2) JWT 기반 인증
토큰에 사용자 정보를 담아 클라이언트가 보유. 서버는 상태를 유지하지 않아 확장성이 뛰어남.
- 로그인 시 서버에서 JWT 토큰 생성 -> 클라이언트 전달
- 클라이언트는 이후 모든 요청에 토큰을 Authorization 헤더에 담아 전달
- 서버는 토큰을 검증 후, 사용자 정보 추출
String token = request.getHeader("Authorization").replace("Bearer ", "");
Claims claims = jwtService.parseToken(token);
장점
- Stateless -> 서버 확장 용이
- 토큰 하나로 인증 가능
단점
- 토큰 만료 및 갱신 처리 복잡
- 토큰 탈취 시 대응 어려움
- 로그아웃 기능 직접 구현 필요
세션 vs JWT
항목 | 세션 기반 인증 | JWT 기반 인증 |
---|---|---|
인증 방식 | 서버가 세션에 사용자 정보를 저장 | 클라이언트가 토큰을 보유하고 인증 |
서버 상태 | 상태가 있음 (Stateful) | 무상태 (Stateless) |
저장 위치 | 서버 메모리, Redis 등 | 클라이언트(쿠키, localStorage 등) |
확장성 | 서버 간 세션 공유 필요 → 확장 어려움 | 서버 간 상태 공유 불필요 → 확장 쉬움 |
보안 위험 | 세션 탈취, 세션 고정 공격 | 토큰 탈취, 재사용 공격 |
유효기간 관리 | 세션 만료 시간 설정 | 토큰에 만료 정보 포함 (exp ) |
복잡도 | 구현 간단, 유지보수 쉬움 | 구현 복잡하지만 유연함 |
정리
- Spring Security는 복잡한 인증/인가 로직을 자동화하여 보안 수준을 높여줌
- 직접 구현하는 방식은 유연하지만, 유지보수나 보안 리스크가 큼
- 세션 기반은 단순하지만 상태 유지가 필요하고, JWT는 무상태로 확장성이 뛰어남
- 서비스의 특성과 규모에 따라 인증 방식과 보안 전략을 적절히 선택하는 것이 중요!