OAuth2 AccessTokenResponseHandler Customize
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);
}
}
}
- 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();
}
}
- 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;
}
}