Spring Security
- 일반적인 공격에 대한 인증, 권한 부여 및 보호 기능을 제공하는 프레임 워크
- 인증과 인가에 대한 부분을 Filter 흐름에 따라 처리
용어
| 구분 | 설명 |
|---|
| Authentication (인증) | 사용자가 누구인지 확인하는 절차 (로그인 과정) |
| Authorization (인가) | 인증된 사용자가 특정 자원에 접근할 수 있는지 판단하는 과정 |
| Principal (접근 주제) | 인증된 사용자 정보(주로 username) |
| Credential (비밀번호) | 사용자 비밀번호 |
| UserDetails | 사용자 정보를 담는 객체 (Spring Security 표준 인터페이스) |
| UserDetailsService | 사용자 정보를 DB에서 조회하는 서비스 (loadUserByUsername) |
| AuthenticationProvider | 실제 인증 로직을 수행하는 컴포넌트 |
- 인증 절차를 거친 후에 인가 절차를 진행, 인가 과정에서 해당 리소스에 대한 접근 권한이 있는지 확인
- 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용
구조

- 클라이언트 요청이 SecurityFilterChain에 진입한
AuthenticationFilter(예: UsernamePasswordAuthenticationFilter)가 요청에서 id/password를 추출
- 이 값을 바탕으로 Authentication 객체를 생성해
AuthenticationManager 에 전달
- AuthenticationManager는 내부에 등록된 여러
AuthenticationProvider 중 적절한 Provider를 선택
- Provider는
UserDetailsService를 통해 DB에서 사용자 정보를 조회
- 조회된 UserDetails와 입력받은 Authentication 객체를 비교해 인증을 수행
- 인증에 성공하면 Authenticated Authentication 객체를 반환
- SecurityContextHolder가 이 인증 객체를 SecurityContext에 저장
- 이후 요청에서는 SecurityContext에 저장된 인증 정보를 기반으로 인가(Authorization)가 처리
주요 모듈
1. Filter
- 요청에서 인증 정보를 추출
- Authentication 객체 생성 후 AuthenticationManager에게 위임
- 성공 → SecurityContext 저장 후 SuccessHandler 호출
- 실패 → FailureHandler 호출
2. Authentication
- 인증 정보 + 권한(Authorities)을 담는 객체
- SecurityContextHolder → SecurityContext → Authentication 순으로 접근
3. UsernamePasswordAuthenticationToken
- 사용자 요청 정보(로그인 시) 또는 인증 완료 정보(인증 후)를 담는 Token 객체
- principal = 사용자명, credentials = 비밀번호
4. AuthenticationProvider
- 로그인 정보 ↔ DB 정보 비교
- 인증 성공하면 인증된 Authentication 반환
- AuthenticationManager가 여러 Provider를 조합함
5. UserDetailsService
- DB에서 유저 정보를 가져오는 역할(loadUserByUsername(String))
6. UserDetails
- 사용자의 정보를 담는 인터페이스, 직접 받아 사용.
- 인증된 토큰을 생성하기 위해 사용.
변경 메서드
| 과거 (Boot 2) | Spring Boot 3 / 4 | 비고 |
|---|
authorizeRequests() | authorizeHttpRequests() | 완전 확정된 API |
antMatchers() | requestMatchers() | 경로 매칭 통합 |
access("hasAnyRole('ROLE_A')") | hasAnyRole("A") | ROLE_ prefix 자동 처리 |
자동 분별 기능 제거 사용법
Spring MVC 경로일 경우
requestMatchers(new MvcRequestMatcher(introspector, "/test1"))
단순 URL
requestMatchers(new AntPathRequestMatcher("/images/**"))
requestMatchers 정리
| 상황 | 사용 |
|---|
| Spring MVC Controller URL | new MvcRequestMatcher(introspector, "/url") |
| 정적 파일, 단순 경로 | new AntPathRequestMatcher("/images/**") |
| CORS pre-flight | CorsUtils::isPreFlightRequest |
- requestMatchers(String)의 자동 구분 기능이 사라짐
- MVC:
MvcRequestMatcher
- static/non-MVC:
AntPathRequestMatcher
- SecurityContextHolder 전략 변경
WebSecurityConfigurerAdapter 완전 제거
- 모든 설정 메서드가 람다 기반 DSL로 통일됨
적용 예시
Config
@Configuration
public class SpringSecurityConfig {
@Bean
PasswordEncoder passwordencoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((request) -> request
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
.requestMatchers("/status", "/images/**", "/view/join", "/auth/join").permitAll()
.requestMatchers("view/setting/admin").hasRole("ADMIN")
.requestMatchers("view/setting/user").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin((login) -> login // form 방식 로그인 사용
.loginPage("/view/login") //만든 로그인 페이지
.loginProcessingUrl("/login-process") //로그인중 url
.usernameParameter("userid") //submit할 아이디
.passwordParameter("pw") //submit할 비밀번호
.defaultSuccessUrl("/view/dashboard", true) //성공 시 dashboard로
.permitAll() // 대시보드 이동이 막히면 안되므로 허용
)
.logout(Customizer.withDefaults()); // 로그아웃은 기본설정으로 (/logout으로 인증해제)
http.logout(logout -> logout.logoutUrl("/logout"));
return http.build();
}
}
UserDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {
private final MemberService memberService;
public MyUserDetailsService(MemberService memberService) {
this.memberService = memberService;
}
@Override
public UserDetails loadUserByUsername(String insertedUserId)
throws UsernameNotFoundException {
Optional<Member> findOne = memberService.findOne(insertedUserId);
Member member = findOne.orElseThrow(
() -> new UsernameNotFoundException("없는 회원입니다 ㅠ왕왕왕왕"));
return User.builder()
.username(member.getUserid())
.password(member.getPw())
.roles(member.getRoles())
.build();
}
}
Controller 에서 로그인 정보를 받기
@GetMapping("/dashboard")
public String dashboardPage(@AuthenticationPrincipal User user, Model model) {
model.addAttribute("loginId", user.getUsername());
model.addAttribute("loginRoles", user.getAuthorities());
return "dashboard";
}