공부/프로그래밍

[spring security, oauth2] invalid_token 에러를 핸들링(ExceptionHandler)하여 output form 설정하기

demonic_ 2021. 2. 17. 08:00

저번 글에 이어 쓴 글이다. 이전에 Oauth2 토큰 발급을 요청할때 Exception을 핸들링 하는 것에 대해 알아봤다.

 

이번에는 ResourceServer 에서 핸들링 하는 방법을 알아보려 한다.

 

유효하지 않은 토큰으로 요청한 경우 다음과 같은 에러를 리턴한다.

 

로그를 살펴보면 다음과 같다

o.s.s.o.p.token.store.JdbcTokenStore     : Failed to find access token for token aaa

 

해당 로그를 찍는 곳으로 찾아가보니 JdbcTokenStore.java 에서 발생한 것을 확인했다.

(이 프로젝트는 jdbc 를 tokenStore로 삼아서 그렇고, 다른 방식으로 TokenStore를 할경우 거기서 에러가 발생할 것이다.

 

잘못된 accessToken은 null을 리턴하고, 메서드를 호출한 상위 메서드는 accessToken이 null일경우 InvalidTokenException 을 던진다.

참고로 이것을 수행하는 클래스는 DefaultTokenService.java 파일 내 231번째 줄인데, 특별한 설정을 하지 않았다면 이 파일을 보면 된다.

 

그리고 이런 Exception들을 캐치하는 곳이 있는데 OAuth2AuthenticationProcessingFilter 파일 내 doFilter에서 수행하다 Exception이 발생해 catch하는 것이다. 흥미로운 것은 authenticationEntryPoint.commence를 호출한다는 점이다. 

 

ResourceServer를 설정할 때 authenticationEntryPoint 를 직접 등록할 수 있는데 이 부분을 교체해서 등록하면 된다. AuthenticationEntryPoint 클래스를 보면 commence 메서드가 보인다.

public interface AuthenticationEntryPoint {
	// ~ Methods
	// ========================================================================================================

	/**
	 * Commences an authentication scheme.
	 * <p>
	 * <code>ExceptionTranslationFilter</code> will populate the <code>HttpSession</code>
	 * attribute named
	 * <code>AbstractAuthenticationProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY</code>
	 * with the requested target URL before calling this method.
	 * <p>
	 * Implementations should modify the headers on the <code>ServletResponse</code> as
	 * necessary to commence the authentication process.
	 *
	 * @param request that resulted in an <code>AuthenticationException</code>
	 * @param response so that the user agent can begin authentication
	 * @param authException that caused the invocation
	 *
	 */
	void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException;
}

 

OAuth2AuthenticationProcessingFilter 에서 authenticationEntryPoint 를 구현한 클래스를 좀더 살펴보자.

해당 클래스는 특별한 설정을 하지 않았다면 OAuth2AuthenticationEntryPoint 파일이다.

public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
...
	private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
...

 

OAuth2AuthenticationEntryPoint 파일은 AbstractOAuth2SecurityExceptionHandler 를 상속받았으며 ExceptionTranslator를 등록할 수 있다. 이전에 본 글인 token 에러 핸들링 글과 비슷한 유형이다.

public class OAuth2AuthenticationEntryPoint extends AbstractOAuth2SecurityExceptionHandler implements
		AuthenticationEntryPoint {
...
public abstract class AbstractOAuth2SecurityExceptionHandler {
...
	private WebResponseExceptionTranslator<?> exceptionTranslator = new DefaultWebResponseExceptionTranslator();
...

 

프레임워크는 여기까지 알아보고 이제 ResourceServer 에서 설정하는 방법을 알아보자

 

 

다음의 순서로 등록한다.

1) WebResponseExceptionTranslator

2) AuthenticationEntryPoint <- webResponseExceptionTranslator

3) ResourceServerSecurityConfigurer <- authenticationEntryPoint

 

ResourceServer 를 설정한 파일을 연다. ResourceServerConfigurerAdapter 를 구현한 파일이다

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
...

 

1번인 WebResponseExceptionTranslator 를 리턴할 메서드를 만든다. 여기서는 구성을 간단하게 message만 넣는다.

...
    public WebResponseExceptionTranslator resourceWebResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                Map responseMap = new HashMap();
                responseMap.put("message", e.getMessage());
                return new ResponseEntity(responseMap, HttpStatus.UNAUTHORIZED);
            }
        };
    }
...

 

이제 이것을 등록할 authenticationEntryPoint 를 생성하여 WebResponseExceptionTranslator 를 등록하고 authenticationEntryPoint 을 ResourceServerSecurityConfigurer 에 등록한다.

...
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        ...
        // 이전 설정에서 다음을 추가        
        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setExceptionTranslator(resourceWebResponseExceptionTranslator());
        resources
                .authenticationEntryPoint(authenticationEntryPoint);
    }
...

 

 

OAuth2AuthenticationEntryPoint 는 이전에 봤던 OAuth2AuthenticationProcessingFilter.java 에서 기본으로 등록한 클래스다.

 

그럼 이제 확인해보자.

 

메세지를 변경하고 싶다면 다음처럼 바꾸면 된다.

...
    public WebResponseExceptionTranslator resourceWebResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                Map responseMap = new HashMap();
                String message = e.getMessage();
                String replaceMessage = message.replace("Invalid access token: ", "유효하지 않은 토큰입니다: ");
                responseMap.put("message", replaceMessage);
                return new ResponseEntity(responseMap, HttpStatus.UNAUTHORIZED);
            }
        };
    }
...

 

 

끝.