Fix oauth model

This commit is contained in:
2025-12-03 11:43:47 +01:00
parent 80b58147ed
commit 39442efb09
3 changed files with 83 additions and 2 deletions

View File

@@ -0,0 +1,28 @@
package com.imgfloat.app.config;
import java.util.Arrays;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
final class OAuth2RestTemplateFactory {
private OAuth2RestTemplateFactory() {
}
static RestTemplate create() {
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()
));
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
if (requestFactory instanceof SimpleClientHttpRequestFactory simple) {
simple.setConnectTimeout(30_000);
simple.setReadTimeout(30_000);
}
return restTemplate;
}
}

View File

@@ -2,11 +2,14 @@ package com.imgfloat.app.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.client.RestTemplate;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@@ -21,9 +24,20 @@ public class SecurityConfig {
.requestMatchers("/ws/**").permitAll() .requestMatchers("/ws/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.oauth2Login(Customizer.withDefaults()) .oauth2Login(oauth -> oauth
.tokenEndpoint(token -> token.accessTokenResponseClient(twitchAccessTokenResponseClient()))
)
.logout(logout -> logout.logoutSuccessUrl("/").permitAll()) .logout(logout -> logout.logoutSuccessUrl("/").permitAll())
.csrf(csrf -> csrf.ignoringRequestMatchers("/ws/**", "/api/**")); .csrf(csrf -> csrf.ignoringRequestMatchers("/ws/**", "/api/**"));
return http.build(); return http.build();
} }
@Bean
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> twitchAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient delegate = new DefaultAuthorizationCodeTokenResponseClient();
RestTemplate restTemplate = OAuth2RestTemplateFactory.create();
restTemplate.setErrorHandler(new TwitchOAuth2ErrorResponseErrorHandler());
delegate.setRestOperations(restTemplate);
return delegate;
}
} }

View File

@@ -0,0 +1,39 @@
package com.imgfloat.app.config;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.util.StreamUtils;
/**
* Twitch occasionally returns error payloads without an {@code error} code field. The default
* {@link OAuth2ErrorHttpMessageConverter} refuses to deserialize such payloads and throws an
* {@link HttpMessageNotReadableException}. That propagates up as a 500 before we can surface a
* meaningful login failure to the user. This handler falls back to a safe, synthetic
* {@link OAuth2Error} so the login flow can fail gracefully.
*/
class TwitchOAuth2ErrorResponseErrorHandler extends OAuth2ErrorResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
try {
super.handleError(response);
} catch (HttpMessageNotReadableException ex) {
throw asAuthorizationException(response, ex);
}
}
private OAuth2AuthorizationException asAuthorizationException(ClientHttpResponse response,
HttpMessageNotReadableException ex) throws IOException {
String body = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
String description = "Failed to parse Twitch OAuth error response" + (body.isBlank() ? "." : ": " + body);
OAuth2Error oauth2Error = new OAuth2Error("invalid_token_response", description, null);
return new OAuth2AuthorizationException(oauth2Error, ex);
}
}