본문 바로가기
  • Where there is a will there is a way.
개발/spring

spring security custom filter 인증 구현

by 사용자 소확행개발자 2020. 5. 20.
http request 에서 custom filter 를 적용하여 특정 header 에 토큰을 담고 해당 토큰이 유효하면 인증된 요청이 되게끔 구현하고 싶었다.

 

스프링 사이트에서 표시된 이미지다.

솔직히 스프링에 대해서 제대로 모르는 사람이 딱 이 그림을 접했을때 무슨생각이 들지 모르겠다. 

위에서 중요한 핵심은 ( custom filter 를 구현할 때 ) 

 

1. AuthenticationFilter

2. SecurityContextHolder.getContext() -> Authentication

3. AuthenticationProviders 

 

이 3개다. 

 

security 에 filter 들이 여러개 기본적으로 구현되어 있는데 난 여기에다가 custom filter 를 추가할 계획이다. 그렇다면 어느 위치에 어떻게 넣어줘야 할까? 

 

filter 위치 정하기

spring 에는 기본적으로 config 라는 개념이 있다. 여기에다가 설정해준다. 

 

@EnableWebSecurity
@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	// 내가 구현하고 싶은 필터
    private final RsaHeaderKeyFilter rsaHeaderKeyFilter;
    // 추후 설명될 거임
    private final TokenAuthenticationProvider tokenAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/add-not-involve-auth").permitAll()
                .anyRequest().authenticated();

		// 어느 위치에 추가할 것인지!
        http.addFilterBefore(rsaHeaderKeyFilter, BasicAuthenticationFilter.class);
        // --> 디버깅 해본 결과 provider 가 두번 타더라 .. 왜그러지 ? 보니
        // 해당 프로바이더를 이렇게 설정해줘서 그런거 같아서 지웠더니 한번만 타더라!
        //http.authenticationProvider(tokenAuthenticationProvider);
    }
}

filter 는 11가지 ? 정도가 구현되어 있는데 내가 구현한 rsaHeaderKeyFilter 는 BasicAuthenticationFilter 전에 추가할거야! 라고 명시해논 config 다.

 

 

filter 구현하기

기본적으로 filter 는 request 를 받아서 해당 필터를 할지 말지 말그대로 필터만 하게끔 스프링에서 가이드 해주고 있다. 

-> 즉 인증은 filter 에서 하지 않고 다른곳에 '인가' 한다.

 

말그대로 필터만

 

@Component
public class RsaHeaderKeyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        if(isContainPaySecurityKey(request)) {
            String rsaKey = request.getHeader("token");
            RsaAuthToken rsaAuthToken = new RsaAuthToken(rsaKey);
            SecurityContextHolder.getContext().setAuthentication(rsaAuthToken);
        }

        filterChain.doFilter(request,response);


    }


    private boolean isContainPaySecurityKey(HttpServletRequest request) {

        String rsaKey = request.getHeader("test");
        return rsaKey != null;
    }
}

spring 에서 addFilterBefore 를 사용할 경우 다음과 같이 OncePerRequestFilter 를  extends 해 사용해야 한다.

해당 @Override 를 보게되면 isContainPaySecurityKey() 로 해당 필터를 사용할지 안할지 구분해 주고

 

해당 필터에서 사용할 인증 객체를 RsaAuthToken 이라고 명명해줬다.

 

인증객체란 ?

위에 filter 에서 SecurityContextHolder.getContext().setAuthentication(rsaAuthToken); 를 보게 되는데 기본적으로 

ContextHolder 에서 인증객체를 다루고 있다. 

public class RsaAuthToken extends AbstractAuthenticationToken{

    private final String principal;
    private Object credentials;

    public RsaAuthToken(String principal){
        super(null);
        this.principal = principal;
        this.credentials = null;
    }


    @Override
    public Object getCredentials() {
        return credentials;
    }


    @Override
    public Object getPrincipal() {
        return principal;
    }
}

 

AbstractAuthenticationToken 을 extends 해서 인증객체를 다룰 수 있다.

 

해당 인증토큰을 구현한 다른 대표적인 애가 UsernamePasswordAuthenticationToken 인데 해당 객체에서는 흔히 

principal 을 id 로 credential 을 password 로 사용하더라 

 

하지만 난 credential 은 딱히 필요가 없다고 생각해서 null 로 두었다.

 

인증 구현하기

현재 filter 를 만들었고 구현 인증을 위한 객체를 만들었고 filter 를 config 에 넣어서 사용하게 끔 설정했다.

그렇다면 filter 에서 구현 객체를 인증하는 로직은 ?

 

스프링에서는 filter 따로 인증을 따로 구현하게끔 설정되어 있다. ( 관심사의 분리 역시.. 스프링.. )

인증을 구현하려면 provider 에서 구현해야 한다.

 

자세한 이미지 보다는 나는 코드랑 디버그로 깨달았다 .. 그리고 이미지를 보니까 이해는 되더라

 

@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {

        RsaAuthToken rsaAuthToken = authentication;
		// 해당 객체를 true 라고 하면 인증이 된다.
        // 여기서 로직을 태워서 만약 인증이 아니라면 throw Authentication 을 던져주면 된다.
        rsaAuthToken.setAuthenticated(true);
		
        return rsaAuthToken;
    }


    @Override
    public boolean supports(Class<?> authentication) {
        // 인증객체에서 선언한 객체는 reflection 을 통해서 해당 객체인지 파악하는데 이용된다.
        return RsaAuthToken.class.isAssignableFrom(authentication);
    }
}

 

자 이제 내부 인증은 세부적으로 구현하면 완성된다!

댓글0