ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SpringSecurity 02
    Spring&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
Designed by Tistory.