Spring Security [Authentication]

김태훈·2023년 7월 19일
0

Spring Security

목록 보기
1/7
post-thumbnail

Spring Security 의 Authentication 공식문서
https://docs.spring.io/spring-security/reference/6.1-SNAPSHOT/servlet/authentication/architecture.html#servlet-authentication-securitycontext

1. 주요 요소

1. SecurityContextHolder

누가 인증(Authenticated) 되었는지 정보를 담고있는 저장소의 개념이다.
아래는 아키텍쳐 사진이다.

SecurityContextHolder 안에 SecurityContext가, 또 그 안에 Principal, Credentials, Authorities 정보가 존재한다.

다음은 해당 아키텍쳐 사진을 예시 코드로 분석한 내용이다.

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);

코드 순서를 보면, 바로 짐작이 가능하다. 가장 먼저 SecurityContextHolder 의 빈 SecurityContext 를 만들고, Authentication 정보를 담은 인스턴스를 생성후, SecurityContextAuthentication 정보를 삽입한다.
마지막에는 Authentication 이 주입된 SecurityContextSecurityContextHolder에 주입한다.

반대로, authentication 을 가져오는 코드 예시를 살펴보자.

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

2. SecurityContext

SecurityContextHolder에서부터 얻게되는 '인증' 정보이다.

3. Authentication

  1. AuthenticationManager에 입력값으로 주어지는 요소로, User가 인증하기 위해 credential을 제공하기 위한 용도로 사용된다.

  2. 현재 인증받은 User정보를 나타낸다. 이는 SecurityContext로부터 얻을 수 있다.

Authentication에는 세가지 구성요소가 있다.

  1. principal
    유저를 확인하기 위해 사용된다. UserDetails의 인스턴스라는데..?

  2. credentials
    비밀번호 같은 것이다. 대부분의 경우, 인증된 후에 clear 된다. (유출되면 안되므로)

  3. authorities
    허가받은 정보를 담은 인스턴스이다.

4. GrantedAuthorityon (i.e. roles, scopes, etc.)

부여받은 권한 정보를 말한다. (roles, scopes 등등..)

Authentication.getAuthorities()

해당 코드로 확인이 가능하다.

5. AuthenticationManager

스프링 시큐리티의 Filter가 Authentication을 어떤식으로 수행하는지에 대해 정의된 API를 말한다.

6. ProviderManager

AuthenticationManger의 구현체이다.
다음은 실제 AuthenticationManager를 구현한 코드 내용이다.

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

아래 그림을 보면 알 수 있는데, 여러 AuthenticationProvider들을 거친다.
AuthenticationProvider들은 Authentication이 성공인지, 실패인지, 알 수 없는지 판단하는 역할을 한다. 만약 어떠한 Provider도 인증여부를 확인할 수 없다면, ProviderNotFoundException 예외를 반환한다.

7. AuthenticationProvider

ProviderManager가 특정 Authentication의 타입을 제공하는데 사용된 Provider이다.

여러 AuthenticationProvider 들을 ProviderManager에 주입하여 사용한다.

처음에, 왜 인증정보를 각기 다른 Provider가 처리해야하는 지가 궁금했다.
이에 대해 다음과 같이 소개한다.

practice each AuthenticationProvider knows how to perform a specific type of authentication. For example, one AuthenticationProvider might be able to validate a username/password, while another might be able to authenticate a SAML assertion. This lets each AuthenticationProvider do a very specific type of authentication while supporting multiple types of authentication and expose only a single AuthenticationManager bean.

여러 타입의 인증정보가 존재하기 때문이었다. (아직 감은 안온다)
예시를 보고 이해할 수 있었다.

Each AuthenticationProvider performs a specific type of authentication. For example, DaoAuthenticationProvider supports username/password-based authentication, while JwtAuthenticationProvider supports authenticating a JWT token.

짠! 깨우쳤다.

8. Request Credentials with AuthenticationEntryPoint

Client로부터 요청받은 credential정보를 처리하는데 사용된다. (login page로 redirect하거나, 인증에 대한 응답을 보내주는 역할)

public class SecurityConfig {

    private final TokenProvider tokenProvider;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    // PasswordEncoder는 BCryptPasswordEncoder를 사용
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // token을 사용하는 방식이기 때문에 csrf를 disable합니다.
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling((exceptionHandling) -> //컨트롤러의 예외처리를 담당하는 exception handler와는 다름.
                        exceptionHandling
                                .accessDeniedHandler(jwtAccessDeniedHandler)
                                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                )

                // enable h2-console
                .headers((headers)->
                        headers.contentTypeOptions(contentTypeOptionsConfig ->
                                headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)))
                // disable session
                .sessionManagement((sessionManagement) ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )

                .authorizeHttpRequests((authorizeRequests)->
                        authorizeRequests
                                //users 포함한 end point 보안 적용 X
                                .requestMatchers("/users/**").permitAll() // HttpServletRequest를 사용하는 요청들에 대한 접근제한을 설정하겠다.
                                .requestMatchers("/error/**").permitAll()
                                .requestMatchers("/swagger-ui/**","/v2/api-docs",
                                        "/swagger-resources",
                                        "/swagger-resources/**",
                                        "/configuration/ui",
                                        "/configuration/security",
                                        "/swagger-ui.html",
                                        "/webjars/**",
                                        /* swagger v3 */
                                        "/v3/api-docs/**",
                                        "/swagger-ui/**").permitAll()
//                                .requestMatchers(PathRequest.toH2Console()).permitAll()// h2-console, favicon.ico 요청 인증 무시
                                .requestMatchers("/favicon.ico").permitAll()
                                .anyRequest().authenticated() // 그 외 인증 없이 접근X
                )
                .exceptionHandling((exceptionHandling)->exceptionHandling
                        .accessDeniedHandler(jwtAccessDeniedHandler)
                        .authenticationEntryPoint(jwtAuthenticationEntryPoint))
                .apply(new JwtSecurityConfig(tokenProvider)); // JwtFilter를 addFilterBefore로 등록했던 JwtSecurityConfig class 적용

        return httpSecurity.build();
    }

}

맨 아랫줄에 보면, exceptionHandling으로 해당 요소를 추가한 것을 볼 수 있다.

9. AbstractAuthenticationProcessingFilter

사용자 정보의 인증을 위한 가장 기본적인 필터이다.
대표적으로 UsernamePasswordAuthenticationFilter가 이친구를 상속받아 사용한다.

사용자가 요청한 credential이 인증을 받기 위해 SpringSecurity는 AuthenticationEntryPoint로 요청을 보낸다.

  1. 사용자가 credential 정보를 보내면, AbstractAuthenticationProcessingFilterHttpServletRequest에 담긴 credential을 토대로 Authentication을 생성한다. 이 때, Authentication의 종류에 따라 다른 AbstractAuthenticationProcessingFilter가 선택이 된다. 예를들어 UsernamePasswordAuthenticationFilterUsernamePasswordAuthenticationToken를 처리하는 역할을 담당한다.

  2. 생성된 Authentication을 AuthenticationManager에 보낸다.

  3. 인증이 실패하면 실패절차를 거친다.
    이때, SecurityContextHolder는 비워지고,
    RememberMeServices.loginFail, AuthenticationFailureHandler 가 동작한다.

  4. 인증이 성공하면 성공절차를 거친다.
    SessionAuthenticationStrategy 가 로그인 여부를 알게된다.
    인증 정보가 SecurityContextHolder에 들어간다.
    RememberMeServices.loginSuccess, AuthenticationSuccessHandler가 발생한다.
    ApplicationEventPublisherInteractiveAuthenticationSuccessEvent를 발생 시킨다.

profile
기록하고, 공유합시다

0개의 댓글