🔥 SpringBoot2.x
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // (테스트 용, 실제는 다르게)
.authorizeRequests()
.antMatchers("/employees/**").authenticated() // 조회는 인증만 있으면 OK
.antMatchers(HttpMethod.PUT, "/employees/**").hasRole("ADMIN") // 수정은 ADMIN만
.and()
.formLogin()
.and()
.httpBasic(); // (간단 로그인 방식 예시)
}
}
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRole()) // 여기에 ROLE_USER, ROLE_ADMIN 자동 prefix 됨
.build();
}
}
🔥 SpringBoot2.x → SpringBoot3.x
- WebSecurityConfigurerAdapter 자체가 deprecated(사용 중단).
- 이유는 → 람다 기반 설정(HttpSecurity) 이 더 직관적이고, 확장성 좋은 방법이라 채택.
- 요즘은 Bean 등록 방식 + 람다 스타일로 설정.
@Configuration + SecurityFilterChain Bean 등록하는 방식
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/employees/**").authenticated() // 인증만 필요
.requestMatchers(HttpMethod.PUT, "/employees/**").hasRole("ADMIN") // 수정은 ADMIN만
)
.formLogin(withDefaults()) // Form Login 사용
.httpBasic(withDefaults()) // HTTP Basic 인증 사용
.build();
}
}
바뀐 포인트
- configure(HttpSecurity) 메소드 오버라이드 안 함.
- 대신, SecurityFilterChain Bean을 등록해서 HttpSecurity를 설정한다.
- 설정 구문도 람다식으로 바뀜 (csrf(csrf -> csrf.disable()) 이런 식으로).
✅ UserDetailsService는?
거의 변함없음.
@Service
public class CustomUserDetailsService implements UserDetailsService {
// 네가 작성한 거랑 똑같이 그대로 사용 가능
}
- 유저를 DB에서 조회해서 UserDetails 만들어주는 역할은 그대로.
- 다만, PasswordEncoder Bean 등록을 명시적으로 해야하는 경우가 많아짐.
✅ PasswordEncoder도 꼭 등록해야 함
Spring Boot 3.x부터는 비밀번호 비교할 때 암호화 필수.
그래서 이런 것도 추가해야 한다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 로그인할 때 비밀번호 매칭용
- 안 하면 에러 나거나 "비밀번호를 인코딩 해야 한다"는 메시지 뜬다.
✨ Spring Boot 3.x 기준 전체 구성 예시
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/employees/**").authenticated()
.requestMatchers(HttpMethod.PUT, "/employees/**").hasRole("ADMIN")
)
.formLogin(withDefaults())
.httpBasic(withDefaults())
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
CustomUserDetailsService는 그대로 쓴다!
🎯 요약
구분 | Spring Boot 2.x 스타일 | Spring Boot 3.x 스타일 |
설정 방법 | extends WebSecurityConfigurerAdapter | @Bean SecurityFilterChain 등록 |
설정 방식 | 메소드 오버라이드 | 람다식 설정 |
PasswordEncoder | 암호화 강제 아님 (기본 허용) | 암호화 강제 (BCrypt 등 등록 필수) |
📑 Q: .authenticated() 이걸로 로그인 되었는지 안되었는지 판단하잖아 로그인이 된건 뭘 기준으로 판단하는거야?
✨ .authenticated()가 의미하는 것
.authorizeHttpRequests(auth -> auth
.requestMatchers("/employees/**").authenticated()
)
- 이건 "이 API는 로그인한 사용자만 접근 가능하다"는 뜻.
- Spring Security 내부에서는 이렇게 해석:
"현재 요청한 사람이 SecurityContext에 인증(Authentication) 정보가 있고, 인증이 완료된 상태여야 한다."
🔥 그럼, "로그인된 상태"를 뭘 기준으로 보냐?
정리하면, Spring Security는
SecurityContextHolder에 Authentication 객체가 존재하고, 그 안에 authenticated=true이면 로그인 된 것.
흐름을 조금 자세히 보면:
- 사용자가 /login, /authenticate 같은 엔드포인트로 아이디/비밀번호를 보낸다.
- Spring Security가 이걸 받아서 AuthenticationManager한테 검증을 맡긴다.
- 예를 들어, UsernamePasswordAuthenticationToken을 만들어서 인증 시도.
- AuthenticationManager가 UserDetailsService를 호출해서
- username으로 유저 조회
- 비밀번호 매칭 (PasswordEncoder.matches)
- 성공하면 Authentication 객체를 만든다.
- 만들어진 Authentication 객체를
- SecurityContextHolder.getContext().setAuthentication(authentication)
이렇게 저장한다.
- SecurityContextHolder.getContext().setAuthentication(authentication)
- 이후부터는 매 요청마다
- SecurityContextHolder에 Authentication이 있는지 확인하고,
- .isAuthenticated() == true 면 "로그인됨"이라고 판단한다.
📌 Authentication 객체 안에 뭐가 들어있냐?
Authentication이라는 건 일종의 로그인 정보 꾸러미.
예를 들면:
필드 | 내용 |
Principal | 로그인한 사용자 객체 (UserDetails) |
Credentials | 보통 비밀번호 (인증 후에는 없어질 수도 있음) |
Authorities | 권한 목록 (ROLE_USER, ROLE_ADMIN 등) |
authenticated | 인증 여부 (true / false) |
🔥 실제 SecurityContextHolder 구조 예시
로그인 성공하면 메모리에 이렇게 저장된다.
SecurityContextHolder
↳ SecurityContext
↳ Authentication (authenticated = true)
↳ Principal (UserDetails)
↳ Authorities ([ROLE_USER])
- 이게 존재하면 .authenticated() 조건을 만족해서 API 접근 허용.
- 없으면 (null이거나 authenticated=false면) 접근 거부 (401 Unauthorized).
📚 요약
구분 | 내용 |
.authenticated() 기준 | SecurityContextHolder에 Authentication이 있고 authenticated=true면 인증된 사용자 |
인증 절차 | 로그인 요청 → AuthenticationManager가 인증 → SecurityContextHolder에 저장 |
내부 저장소 | SecurityContextHolder (스레드별 SecurityContext 저장) |
💡 추가 꿀팁
Spring Security에서 이걸 더 세세하게 쪼개서 제어하고 싶으면 @AuthenticationPrincipal을 쓰기도 한다.
예를 들면,
@GetMapping("/me")
public UserDetails me(@AuthenticationPrincipal UserDetails user) {
return user;
}
🎯 한 줄 요약
.authenticated()는 "SecurityContextHolder에 인증된 Authentication 객체가 존재하면" 로그인 완료로 본다.
📑 Q: AuthenticationManager, SecurityContextHolder 이런 개념이 생소하다.
설명좀 해줘 전체적인 스프링시큐리티 흐름에 대해서 어찌됐든
AuthenticationManager가 검증을 통해 Authentication 객체를 만든다는거고
만들어진 Authentication 객체는 SecurityContextHolder에 저장되는건가
그 홀더는 서버 내에 있는 세션? 클라이언트에서 보내는 뭐를 기준으로
SecurityContextHolder에 Authentication이 있는지 확인하는거지?
🛠 스프링 시큐리티 전체 흐름 (큰 틀)
API 요청이 들어왔을 때 스프링 시큐리티가 인증하는 과정은 이렇다:
요청 → 필터체인 통과 → AuthenticationManager로 인증 시도 → 성공 시 Authentication 생성 → SecurityContextHolder에 저장 → 이후 요청마다 인증 정보 체크
요소 간 역할은:
컴포넌트 | 역할 |
AuthenticationManager | 로그인 인증(인증 성공/실패 판단 담당) |
Authentication | 로그인한 사용자 정보 (아이디, 권한 등) |
SecurityContextHolder | 인증 정보를 저장하는 곳 (서버 메모리 영역, 스레드마다 따로 관리) |
🧠 너가 말한 대로 풀어보자
어찌됐든 AuthenticationManager가 검증을 통해 Authentication 객체를 만든다는거고 만들어진 Authentication 객체는 SecurityContextHolder에 저장되는건가
정답.
✔ AuthenticationManager 가 인증을 "검증" 해.
✔ 성공하면 Authentication 객체를 만들어서,
✔ SecurityContextHolder에 저장해.
✔ 이 Authentication 안에 유저 정보(UserDetails)랑 권한(Authorities)이 들어있어.
📦 그럼 SecurityContextHolder는 정확히 뭔데?
SecurityContextHolder는
"현재 요청한 유저의 Authentication(인증정보)을 들고 있는 서버쪽 저장소"야.
- HTTP 요청이 들어오면
- Spring Security가 SecurityContextHolder에
- "이 요청한 유저 인증정보(Authentication)가 있나?" 확인해.
- 있다면 → "이 사람은 로그인 완료된 사람" 인정.
- 없다면 → 401 Unauthorized 에러.
🧩 그럼 "SecurityContextHolder에 인증 정보가 있는지"를 뭘 보고 확인하냐?
✔ 세션이나 토큰 기반이다.
1. 세션 기반 (Spring Security 기본)
- 로그인할 때 서버 세션이 생성된다. (JSESSIONID)
- 이 세션 ID가 클라이언트 쿠키에 저장된다.
- 클라이언트가 이후 요청마다 JSESSIONID를 같이 보낸다.
- 서버(Spring)는 이 세션ID로 SecurityContext를 찾아서 꺼낸다.
- SecurityContext에 Authentication 있으면 통과.
=> 정리: 클라이언트가 보내는 세션ID 쿠키를 보고 서버에서 SecurityContext를 찾는다.
2. JWT 기반 (커스텀 구현)
- 서버가 세션을 만들지 않고 JWT 토큰을 만들어서 클라이언트에 준다.
- 클라이언트는 Authorization 헤더에 JWT를 실어서 보낸다.
- 서버(Spring Security Filter)가 JWT를 검증해서,
- 직접 SecurityContextHolder에 Authentication을 "수동으로" 넣는다.
- 이후 요청 처리.
=> 정리: 클라이언트가 보내는 토큰을 보고 서버에서 SecurityContext를 세팅한다.
🗺 전체 그림 요약 (세션 기반 기준)
(1) 로그인 요청 (POST /login)
- AuthenticationManager가 인증 (DB 조회 + 비번 검증)
- 인증 성공 → Authentication 객체 생성
- SecurityContextHolder에 Authentication 저장
- 세션에 SecurityContext 저장
- 세션ID(JSESSIONID) 쿠키 발급
(2) 이후 요청
- 클라이언트가 JSESSIONID 쿠키를 같이 보냄
- 서버가 세션ID로 SecurityContext 찾음
- SecurityContextHolder에 Authentication이 있나 확인
- 있으면 로그인 성공한 사람 취급
- 없으면 401 Unauthorized
📚 용어 다시 한 번 정리
용어 | 설명 |
Authentication | 인증된 사용자 정보(Principal, 권한 등) 담은 객체 |
AuthenticationManager | 로그인 요청을 처리해서 인증 성공/실패 판별하는 놈 |
SecurityContextHolder | 현재 요청의 인증(Authentication) 정보를 담는 저장소 (스레드 로컬) |
SecurityContext | SecurityContextHolder 안에 들어있는 객체 (Authentication을 가짐) |
🧤 추가로, 진짜 작은 디테일 하나
- SecurityContextHolder는 기본적으로 ThreadLocal을 쓴다.
- 그래서 "요청 단위(쓰레드 단위)"로 인증 정보가 분리된다.
- (비동기 처리 같은 걸 하면 이거 주의해야 해.)
🔥 정리 한 줄
"AuthenticationManager가 로그인 검증 후 Authentication을 만들고, 그걸 SecurityContextHolder에 저장해두었다가, 요청이 올 때마다 인증 여부를 확인한다."
이제 이거 이해했으면,
👉 다음 레벨은 "JWT를 쓴다면 Authentication은 어떻게 만들어서 SecurityContextHolder에 넣냐" 이거야.
혹시 이어서 JWT 방식 흐름도 이어서 보고 싶어?
📝 Authentication vs Authorization
1. Authentication : 로그인 (인증)
2. Authorization : 권한
보통 인증 후에 권한 체크
비슷해보이지만 다른 두 친구를 소개합니다. Authentication vs Authorization | 아웃풋 트레이닝
내외부 관련된 서비스를 런칭하거나 기능을 추가할때 같이 논의되는 Auth에 대해 들어보셨을텐데요, 보통 Auth라고 하면 Authentication을 말하거나 Authentication + Authorization를 통칭합니다. 이렇게 구분
baek.dev
'spring' 카테고리의 다른 글
[Spring & Vue.js] 공통 코드 그룹 한 번에 요청하고 묶어서 응답받기 (조회 API 설계) (0) | 2025.05.01 |
---|---|
[Spring 테스트] JUnit + Mockito로 공통코드 서비스 테스트하기 (feat. Collectors.toMap, @Mock, @InjectMocks 이해하기) (0) | 2025.05.01 |
Mockito (0) | 2025.04.07 |
싱글톤 Context에서 전략 패턴 사용 시 동시성 문제 (0) | 2025.03.27 |
ThreadLocal, 안 쓰면 큰일 나는 이유 (멀티스레드 동시성 문제 해결법) (0) | 2025.03.20 |
댓글