카테고리 없음

OAuth2 AccessTokenResponseHandler Customize

ssseung 2024. 2. 29. 20:42

respons handler 는

OAuth2TokenEndpointFilter 에 정의되어있다.

private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException {

		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
				(OAuth2AccessTokenAuthenticationToken) authentication;

		OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
		OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
		Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();

		OAuth2AccessTokenResponse.Builder builder =
				OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
						.tokenType(accessToken.getTokenType())
						.scopes(accessToken.getScopes());
		if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
			builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
		}
		if (refreshToken != null) {
			builder.refreshToken(refreshToken.getTokenValue());
		}
		if (!CollectionUtils.isEmpty(additionalParameters)) {
			builder.additionalParameters(additionalParameters);
		}
		OAuth2AccessTokenResponse accessTokenResponse = builder.build();
		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
		this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
	}

 

 

OAuth2TokenEndpointFilter 에 있는 AuthenticationSuccessHandler 는 OAuth2AccessTokenResponseHttpMessageConverter 로 response write 작업을 한다.

 

OAuth2AccessTokenResponseHttpMessageConverter 에 write 에 custom response 를 넣을 수 있게 customize 한 messageConverter 를 넣어줘야한다.

 

AbstractHttpMessageConverter<AccessTokenResponse> 를 상속한 customs converter 를 생성해야한다. (this.accessTokenHttpResponseConverter 대체)

해당 구현체는 readInternal, writeInternal 을 구현해야한다.

 

readInternal 에는 HttpInputMessage를 map으로 바꿔 accessTokenResponseConverter를 통해 tokenResponse객체로 교체하고,

writeInternal 에서는 tokenResponse 객체를 converter 로 map으로 변경한다.

 

OAuth2AccessTokenResponseHttpMessageConverter 에서는

- read 는 DefaultMapOAuth2AccessTokenResponseConverter 를

- write 는 DefaultOAuth2AccessTokenResponseMapConverter 를 사용한다.

이 converter 들도 spring 이 만든 OAuth2AccessTokenResponse 를 대상으로 하기 때문에 custom 하게 만들어줘야한다.


OAuth2TokenEndpointFilter 에 있는 onAuthenticationSuccess 메서드를 구현

@RequiredArgsConstructor
public class AccessTokenResponseHandler implements AuthenticationSuccessHandler {

   /* private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
            new OAuth2AccessTokenResponseHttpMessageConverter();*/
    private final HttpMessageConverter<AccessTokenResponse> accessTokenHttpResponseConverter =
            new AccessTokenResponseHttpConverter();
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
                (OAuth2AccessTokenAuthenticationToken) authentication;
        long between = 0;
        OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
        if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
             between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
        }
        AccessTokenResponse accessTokenResponse = new AccessTokenResponse(
                accessToken.getTokenValue(),
                accessToken.getTokenType().getValue(),
                between
        );

        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
    }
}

/**
 * OAuth2AccessTokenResponseHttpMessageConverter
 * */

public class AccessTokenResponseHttpConverter extends AbstractHttpMessageConverter<AccessTokenResponse> {
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
    private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
    };

    private final Converter<Map<String, Object>, AccessTokenResponse> accessTokenResponseConverter = new CustomMapOAuth2AccessTokenResponseConverter();

    private Converter<AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new CustomAccessTokenResponseMapConverter();
    public AccessTokenResponseHttpConverter() {
        super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
    }
    @Override
    protected boolean supports(Class<?> clazz) {
        return AccessTokenResponse.class.isAssignableFrom(clazz);
    }

    @Override
    protected AccessTokenResponse readInternal(Class<? extends AccessTokenResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

            try {
                Map<String, Object> tokenResponseParameters = (Map<String, Object>) this.jsonMessageConverter
                        .read(STRING_OBJECT_MAP.getType(), null, inputMessage);
                return this.accessTokenResponseConverter.convert(tokenResponseParameters);
            }
            catch (Exception ex) {
                throw new HttpMessageNotReadableException(
                        "An error occurred reading the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex,
                        inputMessage);
            }
    }

    @Override
    protected void writeInternal(AccessTokenResponse accessTokenResponse, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            try {
                Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
                        .convert(accessTokenResponse);
                this.jsonMessageConverter.write(tokenResponseParameters, STRING_OBJECT_MAP.getType(),
                        MediaType.APPLICATION_JSON, outputMessage);
            }
            catch (Exception ex) {
                throw new HttpMessageNotWritableException(
                        "An error occurred writing the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex);
            }
    }
}

  1. read 의 converter
/**
 * public final class DefaultMapOAuth2AccessTokenResponseConverter
 * 		implements Converter<Map<String, Object>, OAuth2AccessTokenResponse>
 * */
public class CustomMapOAuth2AccessTokenResponseConverter implements Converter<Map<String, Object>, AccessTokenResponse>{

    @Override
    public AccessTokenResponse convert(Map<String, Object> source) {
        String accessToken = source.get(OAuth2ParameterNames.ACCESS_TOKEN).toString();
        String tokenType = source.get(OAuth2ParameterNames.TOKEN_TYPE).toString();
        long expiresIn = Long.parseLong(source.get(source).toString());

        return AccessTokenResponse.builder()
                .tokenValue(accessToken)
                .tokenType(tokenType)
                .expiresIn(expiresIn)
                .build();
    }
}
  1. write converter
/**
 * public final class DefaultOAuth2AccessTokenResponseMapConverter
 * 		implements Converter<OAuth2AccessTokenResponse, Map<String, Object>> {
 * */
public class CustomAccessTokenResponseMapConverter implements Converter<AccessTokenResponse, Map<String, Object>> {

    @Override
    public Map<String, Object> convert(AccessTokenResponse tokenResponse) {
        Map<String, Object> parameters = new HashMap<>();
        parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken());
        parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getTokenType());
        parameters.put(OAuth2ParameterNames.EXPIRES_IN, tokenResponse.getExpiresIn());

        return parameters;
    }
}

반응형