Spring Security에 JWT를 잘 녹여보자 (외전)

1

Spring Security에 JWT를 잘 녹여보자
글을 작성 후 PreAuthenticatedAuthenticationProvider 를 사용해봤습니다.

일단 제가 느끼기엔 별로였습니다. 스프링에서 제공하는 AbstractPreAuthenticatedProcessingFilter 의 구상 클래스들을 가져다가 간단하게 오버라이딩해서 쓰기에는 부족한 부분들이 많았습니다.


AbstractPreAuthenticatedProcessingFilter

private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
	Object principal = this.getPreAuthenticatedPrincipal(request);
	if (principal == null) {
		this.logger.debug("No pre-authenticated principal found in request");
	} else {
		this.logger.debug(LogMessage.format("preAuthenticatedPrincipal = %s, trying to authenticate", principal));
		Object credentials = this.getPreAuthenticatedCredentials(request);

		try {
			PreAuthenticatedAuthenticationToken authenticationRequest = new PreAuthenticatedAuthenticationToken(principal, credentials);
			authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
			Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
			this.successfulAuthentication(request, response, authenticationResult);
		} catch (AuthenticationException var7) {
			this.unsuccessfulAuthentication(request, response, var7);
			if (!this.continueFilterChainOnUnsuccessfulAuthentication) {
				throw var7;
			}
		}

	}
}
...
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);

getPreAuthenticatedPrincipal 추상 메서드를 서브클래싱하여 HttpServletRequest 객체에 담긴 어떠한 정보를 통해 Principal을 가져와야합니다.

스프링에서는 org.springframework.security.web.authentication.preauth라는 패키지로 관리하고 있으며
총 5개의 구현체들을 제공하고 있습니다.

  • J2eePreAuthenticatedProcessingFilter
  • RequestAttributeAuthenticationFilter
  • RequestHeaderAuthenticationFilter
  • WebSpherePreAuthenticatedProcessingFilter
  • X509AuthenticationFilter

이 중 저는 Header에 포함되는 Authorization Bearer 를 발라내기 위해 RequestHeaderAuthenticationFilter를 사용하면 되겠다 싶어 사용하기로 했는데요.

결론적으로는 꽝이었습니다. 참고하여 새로 만드는게 맞을 것 같습니다.

필터가 동작하면서 doAuthenticate 가 실행 되고 getPreAuthenticatedPrincipal 메서드를 통해
토큰 정보를 넘겨주어야합니다. 토큰 자체를 넘기는게 아닌 검증 후 유저를 식별할 수 있는 클레임을 넘겨주어야 하는데요. 우선 토큰에 대한 검증을 진행 하고 예외가 발생하였을 때, getPreAuthenticatedPrincipal 에서 처리할 수가 없습니다.

private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
	Object principal = this.getPreAuthenticatedPrincipal(request);
	...
}

예외를 처리하지 않고 바로 던져버리기 때문입니다. 그러면 이 예외는 스프링 컨테이너에서 처리할 수 있는 예외가 아니게 됩니다. 그로 인해 멀쩡한 doFilter를 아래까지 끌고 들어와 예외를 잡아줘야 했습니다.

public class JwtAuthenticationTokenFilter extends RequestHeaderAuthenticationFilter {
	
    ...

	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
		try {
			super.doFilter(request, response, chain);
		} catch (AuthenticationException failed) {
			authenticationEntryPoint.commence((HttpServletRequest) request, (HttpServletResponse) response, failed);
		}
	}
    
	...
    
	@Override
	protected Object getPreAuthenticatedPrincipal(final HttpServletRequest request) {
		String token = resolveFromAuthorizationHeader(request);
		if (token == null) {
			return null;
		}

		try {
			return jwtProvider.extractAccountId(token);
		} catch (Exception e) {
			throw new PreAuthenticatedCredentialsNotFoundException(e.getMessage());
		}
	}
    
    ...
    
}

아무튼 갖다 버렸습니다... 나중에 CAS를 통해 private channel에서 사용되는 서버들을 구축할 일이 생기면 그때 다시 사용해볼지도 모르겠네요.

전 라이브러리들 덕지덕지 붙이는걸 선호하지 않습니다.
OpenFeign 쓰기위해 cloud 붙인다던지, WebClient를 쓰기 위해 webflux를 붙인다던지 말이죠
필요하다면 써야죠! 근데 정말 필요한가를 고민하고 결정합니다.
하지만 여러 도구를 사용해보려고 하고 있습니다. 뭐가 장점이었는지 단점이었는지 까지는 실무수준까지 해보진 못하지만 이런게 있더라, 하면서 기술을 결정할 때 도움이 될테니 말이죠

resource-server-samples

스프링 프로젝트 중 oauth2 resource server를 사용하면 구성 설정 몇 줄로 가능합니다.
위의 샘플 코드를 참고해주세요. 사실 이것도 커스텀하려면 컨버터나 프로바이더의 인터페이스들이 final로 오버라이딩이 막혀있어서 커스텀하기 썩... 좋지는 않습니다. 참고해서 AuthenticationProvider를 새로 만드는게 더 쉽습니다.

근데 이런 방법이라면 PreAuthenticatedAuthenticationProvider 마찬가지겠지만.. :(

0개의 댓글