SpringSecurity를 사용한 인증/인가 프로세스(1) 설정 정보

불바다·2023년 8월 3일

KpopGeneration

목록 보기
2/10

Kpop Generation 프로젝트를 시작하면서 로그인 기능 구현을 위해 SpringSecurity 프레임워크를 사용하기로 결정했다.

SpringSecurity 프레임워크를 사용한 이유

  1. AWS 상에 서버를 배포한다는 가정 하에 보안성 강화를 위해 스프링에서 제공하는 프레임워크를 사용하는 것이 좋다고 판단

  2. 간단한 토이 프로젝트로 향후 서버를 scale-out 하지는 않을 것으로 예상되므로 Session-Cookie 방식을 사용하는 것이 효과적이라고 판단

  3. 프론트엔드 개발자 없이 Thymeleaf를 사용하여 서버 사이드 렌더링 방식으로 프론트엔드단을 구현하므로, JWT 토큰 방식보다 Session-Cookie 방식이 구현하기 더 유리하고 편하다고 판단

  4. 새로운 프레임워크 학습

SecurityConfig 설정

스프링 시큐리티와 관련된 전체 설정입니다.

<주요 특징>
1. 해당 서비스를 운용하는 데 적합한 방식으로 SpringSecurity가 제공하는 인터페이스, 클래스 약 13개를 재설정하여 빈으로 등록
2. Success, Failure Handler를 설정해 사용자가 손쉽게 웹페이지를 탐색할 수 있도록 유도
3. Naver 로그인과 같은 Oauth2 인증 기능 제공

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }
    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new CustomAuthenticationProvider(userDetailsService(), passwordEncoder());
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }
    @Bean
    public AuthenticationDetailsSource formAuthenticationDetailSource() {
        return new FormAuthenticationDetailSource();
    }
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new CustomAuthenticationSuccessHandler();
    }
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new CustomAuthenticationFailureHandler();
    }
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        CustomAccessDeniedHandler customAccessDeniedHandler = new CustomAccessDeniedHandler();
        customAccessDeniedHandler.setErrorPage("/denied");
        return customAccessDeniedHandler;
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
        web
                .ignoring()
                .antMatchers("/resources/**");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();


        http
                .authorizeRequests()
                        .anyRequest().permitAll();
        http
        		.formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/loginProc")
        http
                .addFilterBefore(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);
        http
        		.exceptionHandling()
                .authenticationEntryPoint(new Http403ForbiddenEntryPoint())
                .accessDeniedHandler(accessDeniedHandler());
        http
        		.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler());
        http
        		.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
        http
                .oauth2Login()
                .userInfoEndpoint()
                .userService(oauth2UserService())
        ;


    }

    @Bean
    public CustomOauth2UserService oauth2UserService(){
        return new CustomOauth2UserService();
    }

    @Bean
    public LogoutSuccessHandler customLogoutSuccessHandler(){
        return new CustomLogoutSuccessHandler();
    }

    @Bean
    public FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor.setSecurityMetadataSource(urlFilterInvocationSecurityMetadatasource());
        filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());
        filterSecurityInterceptor.setAuthenticationManager(authenticationManagerBean());
        return filterSecurityInterceptor;
    }
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    public FilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadatasource() throws Exception {
        return new UrlFilterInvocationSecurityMetadatasource(urlResourcesMapFactoryBean().getObject(), securityResourceService());
    }
    @Bean
    public SecurityResourceService securityResourceService(){
        SecurityResourceService securityResourceService = new SecurityResourceService();
        return securityResourceService;
    }
    private UrlResourcesMapFactoryBean urlResourcesMapFactoryBean(){
        UrlResourcesMapFactoryBean urlResourcesMapFactoryBean = new UrlResourcesMapFactoryBean(securityResourceService());
        return urlResourcesMapFactoryBean;
    }
    private AccessDecisionManager affirmativeBased() {
        return new AffirmativeBased(getAccessDecisionVoters());
    }
    private List<AccessDecisionVoter<?>> getAccessDecisionVoters() {
        return Arrays.asList(new RoleVoter());
    }

}


주요 빈에 대한 설명

  1. PasswordEncoder
    DB에 비밀번호를 저장 시 암호화한 저장한다
    또한 PasswordEncoder를 통해서 비밀번호 일치 여부를 판단할 수 있다

  2. CustomUserDetailsService
    UserDetailsService를 상속받아 구현한 클래스로, 로그인 인증 시도 시 DB에 접근해 해당하는 username을 가진 member를 조회해온다

  3. CustomAuthenticationProvider
    AuthenticationProvider를 상속받아 구현한 클래스로, 실제 인증 과정을 담당한다.
    UserDetailsService가 username을 통해 조회해 온 회원 정보(member)의 password를 검증한다.
    인증 성공 시 AuthenticationToken을 만들며, 해당 토큰에는 member 객체와 member 객체의 role이 담겨 있다

  4. FormAuthenticationDetailSource
    AuthenticationDetailsSource를 상속받아 구현한 클래스로, 인증 과정에서 HttpServletRequest에서 필요한 정보를 추출하여 인증 과정에서 사용하는 등 인증에 사용되는 세부 정보 등을 사용할 수 있게 도와준다

  5. CustomAuthenticationSuccessHandler
    SimpleUrlAuthenticationSuccessHandler를 상속받아 구현한 클래스로, 인증에 성공했을 때 사용되는 handler이다.

  6. CustomAuthenticationFailureHandler
    SimpleUrlAuthenticationFailureHandler를 상속받아 구현한 클래스로, 인증에 실패했을 때 사용되는 handler이다

  7. CustomAccessDeniedHandler
    AccessDeniedHandler를 상속받아 구현한 클래스로, 권한 평가(인가 과정)에 실패했을 때 사용되는 handler이다.
    최종적인 필터인 FilerSecurityInterceptor가 최종적으로 사용자의 권한을 평가할 때, 해당 리소스에 접근할 권한이 없을 때 사용된다.

  1. cutstomFilterSecurityInterceptor()
    세부 설정을 거쳐 새로운 FitlerSecurityInterceptor를 만들어 등록한다.
    FilterSecurityInterceptor는 최종적으로 사용자의 권한 정보를 평가하고, 해당 요청을 허용할지 거부할지 결정하는 필터로, SpringSecurity의 가장 마지막 필터이다.
    FitlerSecurityInterceptor를 등록하기 위해서 세 가지 정보가 필요하다
  • SecurityInterceptorMetadatasource : DB에 접근하여 url에 설정되어 있는 권한 정보를 조회해온다
  • AuthenticationDecisionManager : FilterSecurityInterceptor는 AccessManager에게 실제 권한 평가의 수행을 위임한다
    (AUthenticationDecisionManager는 Voter를 통해서 실제 권한 평가를 수행함으로 어떠한 Voter를 사용할 것인지 등록해주어야 한다)
  • AuthenticationManager : 실제로 인증 처리를 담당하는 여러 Provider 중에서 해당 인증 요청을 처리할 수 있는 하나의 Provider를 찾아 인증 요청을 위임하는 역할을 한다
  1. UrlFilterInvocationSecurityMetadatasource
    FilterInvocationSecurityMetadataSource를 상속받아 구현한 클래스이다.
    FilterSecurityInterceptor는 SecurityMetaDatasource로부터 DB에 저장된 각 url의 권한 정보를 조회해오며, AccessDecisionHandler에게 이 url에게 설정된 권한 정보와 현재 접근자의 권한 정보를 비교하도록 요청함으로써 권한 평가를 수행한다
    다시 말해, 최종적으로 권한 평가를 수행함으로써 해당 접근을 허용할지 거부할지 결정하는 FitlerSecurityInterceptor에게 DB에 저장되어 있는 url 권한 설정 정보를 넘겨주는 역할을 한다. 이 정보를 바탕으로 인터셉터는 현재 사용자의 권한 정보와 사용자가 접근하고자 하는 url에 설정되어 있는 권한 정보를 비교해 이 접근을 허용할지 결정한다

  2. UrlResourcesMapFactoryBean
    어플리케이션 최초 동작 시 , DB에 저장되어 있는 url 권한 정보를 추출하여 제공한다

profile
코딩 불바다, 불 같은 코딩, 화끈하게 코딩하자

0개의 댓글