-
SpringSecurity 02Spring&SpringBoot/SpringSecurity 2025. 9. 16. 15:51
Ahthenticaion

SecurityContext / SecurityContextHolder
securityContext
- Authentication 저장 : 현재 인증된 사용자의 Authentication 객체 저장
- ThreadLocal 저장
- 애플리케이션 전반에 걸친 접근성
SecurityContextHolder
- SecurityContext 저장 : 현재 인증된 사용자의 Authentication 객체를 담고 있는 SecurityContext 객체 저장
- SecurityContextHolderStragey 인터페이스 사용하여 다양한 전략 패턴 사용
- 기본 전략 : MODE_THREADLOCAL
- 전략모드 직접 지정 : SecurityContextHolder.setStrategyName(String)
SecurityContextHolder 저장모드
- MODE_THREADLOCAL : 각 스레드가 독립적인 보안 컨텍스트를 가진다 (대부분 서버 환경에 적합)
- ThreadLocalSecurityContextHolderStrategy
- MODE_INHERTIABLETHREADLOCAL : 부모 스레드로부터 자식 스레드로 보안 컨텍스트가 상속되어 작업을 스레드 간 분산 실행하는 경우 유용
- InheritableThreadLocalSecurityContextHolderStrategy
- MODE_GLOBAL : 전역적으로 단일 보안 컨텍스트를 사용하며 서버 환경에서는 부적합하여 주로 애플리케이션에 적합
- GlobalSecurityContextHolderStrategy
SecurityContextHolder & SecurityContext
- 스레드마다 할당 되는 전용 저장소에 SecurityContext를 저장하기 때문에 동시성 문제가 없다
- 스레드 풀에서 운영되는 스레드일 경우 새로운 요청이라도 기존의 ThreadLocal이 재사용될 수 있기 때문에 클라이언트로 응답 직전에 항상 SecurityContext를 삭제해야 한다
@RestController public class IndexController { @Autowired SecurityContextService securityContextService; @GetMapping("/") public String index() { SecurityContext securityContext = SecurityContextHolder.getContextHolderStrategy().getContext(); Authentication authentication = securityContext.getAuthentication(); System.out.println("Controller - authentication : " + authentication); securityContextService.securityContext(); return "index"; } ... }@Service public class SecurityContextService { public void securityContext() { // 파라미터 전달 없이 사용, 전역적으로 사용할 수 있음 SecurityContext securityContext = SecurityContextHolder.getContextHolderStrategy().getContext(); Authentication authentication = securityContext.getAuthentication(); System.out.println("Service - authentication : " + authentication); } }
AuthenticationManager
AuthenticationManager
- 인증 필터로부터 Authentication 객체를 받아 인증 시도하며 인증 성공할 경우 Authentication 객체 반환
- AuthenticationProvider 관리하며 AuthenticationProvider 목록을 순차적으로 순회하며 인증 요청 처리
- AuthenticationProvider 목록 중 인증 처리 요건에 맞는 적절한 AuthenticationProvider 찾아 인증처리 위임
- AuthenticationManagerBuilder에 의해 객체 생성되며 주로 사용하는 구현체로 ProviderManager가 제공
- AuthenticationManager authenticationManager = authenticationManagerBuilder.build() : 최초 한 번만 호출 가능
- AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject() : build() 후에는 getObject()로 참조
AuthenticationManagerBuilder
- AuthenticationManager 객체를 생성하며 UserDeatilService 및 AuthenticationProvider를 추가
- Httpsecurity.getSharedObject( AuthenticationManagerBuilder .class)를 통해 객체 참고 가능
AuthenticationManager 사용 방법 1) HttpSecurity 사용
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSahredObject(AuthenticationManagerBuilder.class); AuthenticationManager authenticationManager = authenticationManagerBuilder.build(); AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject(); http .authorizeHttpRequest(auth -> auth .requestMatchers("/api/login").permitAll() .anyRequest().authenticated()) .authenticationManager(authenticationManager) .addFilterBefore(customFilter(http, authenticationManager), UsernamePasswordAuthenticationFilter.class); return http.build(); } public CustomAuthenticationFilter customFilter(Authentication athentication) throws Exception { CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(); customAuthenticationFilter.setAuthenticationManager(authenticationManager); return customAuthenticationFilter; }AuthenticationManager 사용 방법 2) 직접 생성
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); http.formLogin(Customizer.withDefaults()); http.addFilterBefore(customFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public CustomAuthenticationFilter customFilter() { List<AuthenticationProvider> list1 = List.of(new DaoAuthenticationProvider()); ProviderManager parent = new ProviderManager(list1); List<AuthenticationProvider> list2 = List.of(new AnonymousAuthenticationProvider("key"), new CustomAuthenticationProvider())); ProviderManager authenticationManager = new ProviderManager(list2, parent); CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(); customAuthenticationFilter.setAuthenticationManager(authenticationManager); return customAuthenticationFilter; }- V1
@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); AuthenticationManager manager = builder.build(); http.authorizeHttpRequests(auth -> auth .requestMatchers("/", "/api/login").permitAll() .anyRequest().authenticated()) .authenticationManager(manager) .addFilterBefore(customAuthenticationFilter(http, manager), UsernamePasswordAuthenticationFilter.class); return http.build(); } public CustomAuthenticationFilter customAuthenticationFilter(HttpSecurity http, AuthenticationManager manager) { CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(http); customAuthenticationFilter.setAuthenticationManager(manager); return customAuthenticationFilter; } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); return new InMemoryUserDetailsManager(user); } }public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private final ObjectMapper objectMapper = new ObjectMapper(); public CustomAuthenticationFilter(HttpSecurity http) { // super(new AntPathRequestMatcher("/api/login", "GET")); super(PathPatternRequestMatcher .withDefaults() .matcher(HttpMethod.GET, "/api/login")); setSecurityContextRepository(getSecurityContextRepository(http)); } private SecurityContextRepository getSecurityContextRepository(HttpSecurity http) { SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class); if (securityContextRepository == null) { securityContextRepository = new DelegatingSecurityContextRepository( new RequestAttributeSecurityContextRepository(), new HttpSessionSecurityContextRepository()); } return securityContextRepository; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); return this.getAuthenticationManager().authenticate(token); } }- V2
@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers("/", "/api/login").permitAll() .anyRequest().authenticated()) .addFilterBefore(customAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class); return http.build(); } public CustomAuthenticationFilter customAuthenticationFilter(HttpSecurity http) { List<AuthenticationProvider> list1 = List.of(new DaoAuthenticationProvider()); ProviderManager parent = new ProviderManager(list1); List<AuthenticationProvider> list2 = List.of(new AnonymousAuthenticationProvider("key"), new CustomAuthenticationProvider()); ProviderManager providerManager = new ProviderManager(list2, parent); CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(http); customAuthenticationFilter.setAuthenticationManager(providerManager); return customAuthenticationFilter; } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); return new InMemoryUserDetailsManager(user); } }public class CustomAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String loginID = authentication.getName(); String password = authentication.getCredentials().toString(); return new UsernamePasswordAuthenticationToken(loginID, null, List.of(new SimpleGrantedAuthority("ROLE_USER"))); } @Override public boolean supports(Class<?> authentication) { return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class); } }AuthenticationProvider
- 사용자의 자격 증명을 확인하고 인증 과정을 관리하는 클래스로서 사용자가 시스템에 엑세스하기 위해 제공한 정보가 유효한지 검증하는 과정을 포함
- 표준 사용자 이름, 비밀번호 기반으로 한 인증, 토큰 기반 인증, 지문 인식 등을 처리
- 성공한 후 Authentication 객체를 반환하며 이 객체에는 사용자의 신원 정보와 인증된 자격을 포함
- 실패한 후 AuthenticationException 예외 발생
AuthenticationProvider 사용 방법 1) 일반 객체로 생성
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 1 AuthenticationManagerBuilder managerBuilder = http.getSharedObjkect(AuthenticationManagerBuilder.class); managerBuilder.authenticationProvider(new CustomAuthenticationProvider()); // 2 http.authenticationProvider(new CustomAutheticationProviderV2()); http.authorizeHttpRequest(auth -> auth.anyRequest().authenticated()); http.formLogin(Customizer.withDefault()); return http.build(); }AuthenticationProvider 사용 방법 2-1) 빈 1개만 정의해서 생성
- AuthenticationProvider 빈으로 정의하면 DaoAuthenticationProvider를 자동으로 대체하게 된다
@Bean public AuthenticationProvider customAuthenticationProvider() { return new CustomAuthenticationProvider(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManagerBuilder builder, AuthenticationConfiguration configuration) throws Exception { AuthenticationManagerBuilder managerBuilder = http.getSharedObjkect(AuthenticationManagerBuilder.class); managerBuilder.authenticationProvider(customAuthenticationProvider()); ProviderManager providerManager = (ProviderManager)configuration.getAuthenticationManager(); providerManager.getProviders().remove(0); builder.authenticationProvider(new DaoAuthenticationProvider()); http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); return http.build(); }AuthenticationProvider 사용 방법 2-2) 빈 두 개 이상 정의해서 생성
@Bean public AuthenticationProvider customAuthenticationProvider() { return new CustomAuthenticationProvider(); } @Bean public AuthenticationProvider customAuthenticationProviderV2() { return new customAuthenticationProviderV2(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManagerBuilder builder, AuthenticationConfiguration configuration) throws Exception { AuthenticationManagerBuilder managerBuilder = http.getSharedObjkect(AuthenticationManagerBuilder.class); managerBuilder.authenticationProvider(customAuthenticationProviderV2()); managerBuilder.authenticationProvider(customAuthenticationProviderV2()); http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); http.formLogin(Customizer.withDefaults()); return http.build(); }1. 일반객체로 생성
public class CustomAuthenticationProviderV2 implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String login = authentication.getName(); String password = authentication.getCredentials().toString(); // 아이디 검증 // 비밀번호 검증 return new UsernamePasswordAuthenticationToken(login,password, List.of(new SimpleGrantedAuthority("ROLE_USER"))); } @Override public boolean supports(Class<?> authentication) { return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class); } }public class CustomAuthenticationProviderV3 implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String login = authentication.getName(); String password = authentication.getCredentials().toString(); // 아이디 검증 // 비밀번호 검증 return new UsernamePasswordAuthenticationToken(login,password, List.of(new SimpleGrantedAuthority("ROLE_USER"))); } @Override public boolean supports(Class<?> authentication) { return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class); } }@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 방법 1. AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); builder.authenticationProvider(new CustomAuthenticationProviderV2()); builder.authenticationProvider(new CustomAuthenticationProviderV3()); http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) // 방법 2. // .authenticationProvider(new CustomAuthenticationProviderV2()) // .authenticationProvider(new CustomAuthenticationProviderV3()); .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); return new InMemoryUserDetailsManager(user); } }2. 빈 1개로 정의 후 설정
@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) // 방법 2. // .authenticationProvider(new CustomAuthenticationProviderV2()) // .authenticationProvider(new CustomAuthenticationProviderV3()); .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean public AuthenticationProvider authenticationProvider() { return new CustomAuthenticationProvider(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); return new InMemoryUserDetailsManager(user); } }@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManagerBuilder builder, AuthenticationConfiguration configuration) throws Exception { // 원래의 셋팅을 유지하고 싶은 경우 AuthenticationManagerBuilder manangerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); manangerBuilder.authenticationProvider(customAuthenticationProvider()); // 삭제 ProviderManager authenticationManager = (ProviderManager)configuration.getAuthenticationManager(); authenticationManager.getProviders().remove(0); // 추가 builder.authenticationProvider(new DaoAuthenticationProvider()); http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) ; return http.build(); }3. 빈 2개로 정의 후 설정 (흔하지 않은 경우)
@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); builder.authenticationProvider(customAuthenticationProvider()); builder.authenticationProvider(customAuthenticationProviderV2()); http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean public AuthenticationProvider customAuthenticationProvider() { return new CustomAuthenticationProvider(); } @Bean public AuthenticationProvider customAuthenticationProviderV2() { return new CustomAuthenticationProviderV2(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); return new InMemoryUserDetailsManager(user); } }UserDeatilsService
- 사용자 관련된 상세 데이터 로드
- 이 인터페이스를 사용하는 클래스는 주로 AuthenticationProvider이며 사용자가 시스템에 존재하는지 여부와 사용자 데이터를 검색하고 인증 과정을 수행
public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return User.withUsername("user").password("{noop}1111").roles("USER").build(); } }@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } }@Component @RequiredArgsConstructor public class CustomAuthenticationProviderV2 implements AuthenticationProvider { private final UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String login = authentication.getName(); String password = authentication.getCredentials().toString(); // 아이디 검증 UserDetails user = userDetailsService.loadUserByUsername(login); if (user == null) { throw new UsernameNotFoundException("User not found"); } // 비밀번호 검증 return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class); } }UserDetails
- 사용자의 기본 정보를 저장하는 인터페이스로서 Spring Security에서 사용하는 사용자 타입
- 저장된 정보는 인증 절차에서 사용되기 위해 Authentication 객체에 포함되어 구현체로 User 클래스가 제공된다
@Getter @AllArgsConstructor public class AccountDto { private String username; private String password; private Collection<GrantedAuthority> authorities; }public class CustomUserDetails implements UserDetails { private final AccountDto account; public CustomUserDetails(AccountDto account) { this.account = account; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return account.getAuthorities(); } @Override public String getPassword() { return account.getPassword(); } @Override public String getUsername() { return account.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AccountDto accountDto = new AccountDto("user", "{noop}1111", List.of(new SimpleGrantedAuthority("ROLE_USER"))); return new CustomUserDetails(accountDto); } }728x90'Spring&SpringBoot > SpringSecurity' 카테고리의 다른 글
SpringSecurity 06 (0) 2025.09.25 SpringSecurity 05 (0) 2025.09.24 SpringSecurity 04 (0) 2025.09.18 SpringSecurity 03 (0) 2025.09.17 SpringSecurity 01 (0) 2025.09.15