Skip to content

Spring Security

aegis1920 edited this page Jul 30, 2020 · 1 revision

Spring Security

1. 필터

스프링 시큐리티는 필터를 사용합니다.

  • 필터는 인터셉터와 비슷하지만, 서블릿보다도 더 앞에 있습니다.
  • 필터는 클라이언트 요청이 서블릿으로 가기 전에 먼저 처리할 수 있도록 톰캣(WAS)에서 지원해주는 기능입니다.

Spring%20Security%204a06dfdbd9ef456783d2296ba698a51f/Untitled.png

2. 스프링 시큐리티 필터 체인

아래 그림에 보시면 SecurityFilterChain 부분이 있는데요.

HttpRequest 가 서블릿에 도달하기 전에, 먼저 필터들을 위에서부터 통과합니다.

HttpResponse 가 나가기 전에, 필터들을 아래에서부터 통과합니다.

- 위 두 줄은 제가 기억나는대로 쓴건데 확실하지 않아요 -

Spring%20Security%204a06dfdbd9ef456783d2296ba698a51f/Untitled%201.png

  • SecurityContextPersistentFilter
    • SecurityContextRepository에서 SecurityContext를 가져와서 SecurityContextHolder에 주입하거나 반대로 저장하는 역할을 합니다.
  • LogoutFilter
    • logout 요청을 감시하며, 요청시 인증 주체(Principal)를 로그아웃 시킵니다.
  • UsernamePasswordAuthenticationFilter
    • login 요청을 감시하며, 인증 과정을 진행합니다.
  • DefaultLoginPageGenerationFilter
    • 사용자가 별도의 로그인 페이지를 구현하지 않은 경우, 스프링에서 기본적으로 설정한 로그인 페이지로 넘어가게 합니다.
  • BasicAuthenticationFilter
    • HTTP 요청의 (BASIC)인증 헤더를 처리하여 결과를 SecurityContextHolder에 저장합니다.
  • RememberMeAuthenticationFilter
    • SecurityContext에 인증(Authentication) 객체가 있는지 확인하고 RememberMeServices를 구현한 객체 요청이 있을 경우, RememberMe를 인증 토큰으로 컨텍스트에 주입합니다.
  • AnonymousAuthenticationFilter
    • 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 익명 사용자로 취급합니다.
  • SessionManagementFilter
    • 요청이 시작된 이후 인증된 사용자인지 확인하고, 인증된 사용자일 경우 SessionAuthenticationStrategy를 호출하여 세션 고정 보호 매커니즘을 활성화 하거나 여러 동시 로그인을 확인하는 것과 같은 세션 관련 활동을 수행합니다.
  • ExceptionTranslationFilter
    • 필터체인 내에서 발생되는 모든 예외를 처리합니다.
  • FilterSecurityInterceptor
    • AccessDecisionManager로 권한부여처리를 위임하고 HTTP 리소스의 보안 처리를 수행합니다.

3. 커스텀 필터 만들기

우리 프로젝트의 config 패키지에 JwtAuthenticationFilter 클래스가 있습니다.

이 클래스가 저희의 커스텀 필터입니다.

  • 이 필터의 목적
    • 사용자가 자원에 접근 할 때 (ex 내 정보 조회), 헤더의 토큰을 검사해서

      유효한 토큰이 아니면 접근을 못하게 하는 목적입니다.

// config/JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends GenericFilterBean {

    private final JwtTokenDecoder jwtTokenDecoder;

    public JwtAuthenticationFilter(JwtTokenDecoder jwtTokenDecoder) {
        this.jwtTokenDecoder = jwtTokenDecoder;
    }
~~~~
   @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        String token = ((HttpServletRequest) request).getHeader("X-AUTH-TOKEN");

        if (isValidToken(token)) {
						// 토큰으로부터 Authentication 객체를 얻습니다.
            Authentication authentication = jwtTokenDecoder.getAuthentication(token);

						// SecurityContext 에 Authentication 객체를 저장합니다.
            SecurityContextHolder.getContext()
                .setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }

    private boolean isValidToken(String token) {
        return Objects.nonNull(token)
            && jwtTokenDecoder.isValidToken(token);
    }
}
  • 토큰이 유효한 경우, 토큰으로부터 Authentication 객체를 얻어서 SecurityContext 영역에 저장합니다.

3.1. Authentication

  • 어떤 사용자가 토큰을 담아서 요청을 보내면, 필터는 이 요청으로부터 Authentication 을 얻습니다.

  • Authentication 객체에는 해당 유저의 Role (ex :일반사용자인가 ? 관리자인가?) 등

    유저정보가 들어있습니다.

Authentication 객체에 대한 설명은 3.2로 이어집니다~

3.2. 접근주체, Authentication, SecurityContext

  • 어떤 자원에 대한 접근 요청이 들어오면, 이 요청은

    일반사용자의 요청인지, 관리자의 요청인지, 익명 사용자의 요청인지 등

    요청을 보낸 주체(principle)가 있습니다.

  • 주체(principle)는 자신에 대한 Authentication 이 존재하고, 이 Authentication 객체는 Security Context 에 저장되어 관리됩니다.

3.3. SecurityContext

  • 필터 체인의 필터들은 순서대로 실행되는데, 모든 필터들은 Security Context 라는 영역에 접근할 수 있습니다.
  • Security Context 라는 공간은 한 번의 요청에서 사용되고 난 뒤 없어집니다.

4. 커스텀 필터를 Security 필터 체인에 연결하기

  • 우리 프로젝트의 config 패키지에 WebSecurityConfig 클래스가 있는데요.

    configure 메서드를 보시면 addFilterBefore(..) 이 부분이 있습니다.

    이 부분이 커스텀 필터를 시큐리티 필터 체인에 연결하는 부분입니다.

  • 우리 코드에서는 JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 넣고 있습니다.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtTokenDecoder jwtTokenDecoder;

    public WebSecurityConfig(JwtTokenDecoder jwtTokenDecoder) {
        this.jwtTokenDecoder = jwtTokenDecoder;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .httpBasic().disable() // rest api 만을 고려하여 기본 설정은 해제하겠습니다.
            .csrf().disable() // csrf 보안 토큰 disable처리.
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
									// 토큰 기반 인증이므로 세션 역시 사용하지 않습니다.
            .and()
            .authorizeRequests() // 요청에 대한 사용권한 체크
            .antMatchers("/users/**").hasRole("USER")
            .anyRequest().permitAll() // 그외 나머지 요청은 누구나 접근 가능
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(jwtTokenDecoder),
                UsernamePasswordAuthenticationFilter.class);
						/* JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣는다 */
    }
}