Use OAuth 2.0 Token Introspection during log in ()

Currently, it uses an endpoint similar to OpenID Connect UserInfo but with some differences. The endpoint does not require the "openid" scope for example. There is an ongoing effort to replace the OAuth 2.0 authorization server with a more standard compliant one which would break the endpoint (since it would require the "openid" scope). It is currently not possible to request the "openid" scope to future-proof since Spring would act differently if that scope is present and assume full OpenID Connect. That leads to requiring an id token to have been issued which the current authorization server does not do.

To get around this the implementation is changed to use a standard compliant Token Introspection endpoint to get access to the subject of the access token (which is the only part that's necessary right now). Since the endpoint is standard compliant it will work with any future authorization server.

It may be necessary to run docker compose up --build to get the latest version of the Toker containers.

Co-authored-by: Andreas Svanberg <andreass@dsv.su.se>
Reviewed-on: 
Reviewed-by: Andreas Svanberg <andreass@dsv.su.se>
Co-authored-by: Nico Athanassiadis <nico@dsv.su.se>
Co-committed-by: Nico Athanassiadis <nico@dsv.su.se>
This commit is contained in:
Nico Athanassiadis 2025-03-25 15:48:32 +01:00 committed by Andreas Svanberg
parent 263774f74b
commit f7466a57df
4 changed files with 69 additions and 2 deletions

@ -0,0 +1,42 @@
package se.su.dsv.seshat;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
public class TokenIntrospectionRequestEntityConverter implements Converter<OAuth2UserRequest, RequestEntity<?>> {
private static final MediaType FORM_URL_ENCODED = MediaType.valueOf(
MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"
);
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
URI uri = UriComponentsBuilder.fromUriString(
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()
)
.build()
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
headers.setAccept(Collections.singletonList(MediaType.ALL));
headers.setContentType(FORM_URL_ENCODED);
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.TOKEN, userRequest.getAccessToken().getTokenValue());
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
}

@ -0,0 +1,25 @@
package se.su.dsv.seshat.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import se.su.dsv.seshat.TokenIntrospectionRequestEntityConverter;
@Configuration
public class SeshatConfiguration {
// Stop gap measure to switch to Token Introspection instead of OIDC UserInfo
// endpoint. This is necessary because the UserInfo endpoint will in soon require
// the "openid" scope, which is not granted to our clients. Unfortunately we can't
// request the scope because that makes Spring require an id token in the token
// exchange which is not granted at the moment.
//
// Once a new authorization server is in place we can remove this bean and use
// straight up id tokens with "openid" scope.
@Bean
public DefaultOAuth2UserService defaultOAuth2UserService() {
DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
defaultOAuth2UserService.setRequestEntityConverter(new TokenIntrospectionRequestEntityConverter());
return defaultOAuth2UserService;
}
}

@ -35,7 +35,7 @@ public class CustomOAuth2loginSuccessHandler implements AuthenticationSuccessHan
String email = oAuth2User.getAttribute("mail") != null ? oAuth2User.getAttribute("mail") : "no-email";
if(!userService.existsByUsername(oAuth2User.getAttribute("principal"))) {
if(!userService.existsByUsername(username)) {
userService.registerUser(username, email);
}
response.sendRedirect(redirectUrl);

@ -32,7 +32,7 @@ spring.jpa.show-sql=false
# OAuth2 properties, remember if you change the registration.provider the provider properties must be updated
spring.security.oauth2.client.provider.docker.authorization-uri=http://localhost:51337/authorize
spring.security.oauth2.client.provider.docker.token-uri=http://localhost:51337/exchange
spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:51337/verify
spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:51337/introspect
spring.security.oauth2.client.provider.docker.user-name-attribute=sub
spring.security.oauth2.client.registration.seshat.client-id=seshat
spring.security.oauth2.client.registration.seshat.client-secret=n0tS3cr3t