Expected Behavior
One should be able to use the native build features of Spring Boot out of the box with .spring-boot-starter-oauth2-client.
I'm just doing the most naive login thru Google use case.
Current Behavior
Doesn't work out of the box. One need to resort to using the native-image-agent to collect the necessary introspection data.
Workaround
A RuntimeHintsRegistrar should be easy add.
Adding the following to META-INF/native-image/serialization-config.json fixes the issue:
{
"types":[
{
"name":"java.lang.Boolean"
},
{
"name":"java.lang.String"
},
{
"name":"java.net.URL"
},
{
"name":"java.time.Instant"
},
{
"name":"java.time.Ser"
},
{
"name":"java.util.ArrayList"
},
{
"name":"java.util.Collections$UnmodifiableCollection"
},
{
"name":"java.util.Collections$UnmodifiableList"
},
{
"name":"java.util.Collections$UnmodifiableMap"
},
{
"name":"java.util.Collections$UnmodifiableRandomAccessList"
},
{
"name":"java.util.Collections$UnmodifiableSet"
},
{
"name":"java.util.HashMap"
},
{
"name":"java.util.HashSet"
},
{
"name":"java.util.LinkedHashMap"
},
{
"name":"java.util.LinkedHashSet"
},
{
"name":"org.springframework.security.authentication.AbstractAuthenticationToken"
},
{
"name":"org.springframework.security.core.authority.SimpleGrantedAuthority"
},
{
"name":"org.springframework.security.core.context.SecurityContextImpl"
},
{
"name":"org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"
},
{
"name":"org.springframework.security.oauth2.core.AbstractOAuth2Token"
},
{
"name":"org.springframework.security.oauth2.core.AuthorizationGrantType"
},
{
"name":"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest"
},
{
"name":"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType"
},
{
"name":"org.springframework.security.oauth2.core.oidc.OidcIdToken"
},
{
"name":"org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser"
},
{
"name":"org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority"
},
{
"name":"org.springframework.security.oauth2.core.user.DefaultOAuth2User"
},
{
"name":"org.springframework.security.oauth2.core.user.OAuth2UserAuthority"
},
{
"name":"org.springframework.security.web.authentication.WebAuthenticationDetails"
}
],
"lambdaCapturingTypes":[
],
"proxies":[
]
}
Comment From: marcusdacoregio
Hi @rcomblen. Thanks for the report.
Can you provide more details about your application? Ideally, a minimal, reproducible sample would be great. I tried myself using the oauth2-login sample and it worked out of the box. I just had to tweak the Thymeleaf template (not related to Spring Security tho).
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-projects-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
Comment From: rcomblen
Hey @marcusdacoregio
Here is the kind of exception I get:
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: SerializationConstructorAccessor class not found for declaringClass: org.springframework.security.oauth2.core.OAuth2AuthorizationException (targetConstructorClass: java.lang.Object). Usually adding org.springframework.security.oauth2.core.OAuth2AuthorizationException to serialization-config.json fixes the problem.
It's pretty painful to work with, as native compilation takes a while, so feedback cycle is very long.
Where is the instrumentation performed in the source code of Spring Security ? Maybe I have something that prevents it from being loaded properly.
Comment From: BenEfrati
I faced an issue when using spring session jdbc,
HttpSessionOAuth2AuthorizedClientRepository calls to session.setAttribute with OAuth2AuthorizedClient.
There are no runtime hints registered that support serializing OAuth2AuthorizedClient.
Can be fixed by register runtime hints:
public class OAuth2ClientSessionRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.serialization().registerType(OAuth2AuthorizedClient.class);
hints.serialization().registerType(ClientRegistration.class);
hints.serialization().registerType(ClientAuthenticationMethod.class);
hints.serialization().registerType(AuthorizationGrantType.class);
hints.serialization().registerType(ClientRegistration.ProviderDetails.class);
hints.serialization().registerType(ClientRegistration.ClientSettings.class);
hints.serialization().registerType(ClientRegistration.ProviderDetails.UserInfoEndpoint.class);
hints.serialization().registerType(AuthenticationMethod.class);
hints.serialization().registerType(OAuth2AccessToken.class);
hints.serialization().registerType(OAuth2AccessToken.TokenType.class);
hints.serialization().registerType(OAuth2RefreshToken.class);
hints.serialization().registerType(AbstractOAuth2Token.class);
hints.serialization().registerType(TypeReference.of("java.util.Collections$EmptySet"));
hints.serialization().registerType(TypeReference.of("java.util.Collections$EmptyMap"));
hints.serialization().registerType(TypeReference.of("java.util.Collections$UnmodifiableMap"));
hints.serialization().registerType(TypeReference.of("java.util.Collections$UnmodifiableSet"));
hints.serialization().registerType(TypeReference.of("java.time.Ser"));
hints.serialization().registerType(HashMap.class);
hints.serialization().registerType(HashSet.class);
hints.serialization().registerType(LinkedHashSet.class);
hints.serialization().registerType(LinkedHashMap.class);
hints.serialization().registerType(Instant.class);
}
}
Comment From: Mr-DeWitt
I tried to use BenEfrati's answer, but I needed some more extra types to register. My final working config looks like this:
public class SecurityOAuth2RuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.serialization()
// The extra types
.registerType(OAuth2AuthorizationRequest.class)
.registerType(OAuth2AuthorizationResponseType.class)
.registerType(DefaultOAuth2User.class)
.registerType(Boolean.class)
.registerType(SimpleGrantedAuthority.class)
.registerType(OAuth2Error.class)
// From the mentioned answer
.registerType(OAuth2AuthorizedClient.class)
.registerType(ClientRegistration.class)
.registerType(ClientAuthenticationMethod.class)
.registerType(AuthorizationGrantType.class)
.registerType(ClientRegistration.ProviderDetails.class)
.registerType(ClientRegistration.ClientSettings.class)
.registerType(ClientRegistration.ProviderDetails.UserInfoEndpoint.class)
.registerType(AuthenticationMethod.class)
.registerType(OAuth2AccessToken.class)
.registerType(OAuth2AccessToken.TokenType.class)
.registerType(OAuth2RefreshToken.class)
.registerType(AbstractOAuth2Token.class)
.registerType(TypeReference.of("java.util.Collections$EmptySet"))
.registerType(TypeReference.of("java.util.Collections$EmptyMap"))
.registerType(TypeReference.of("java.util.Collections$UnmodifiableMap"))
.registerType(TypeReference.of("java.util.Collections$UnmodifiableSet"))
.registerType(TypeReference.of("java.time.Ser"))
.registerType(HashMap.class)
.registerType(HashSet.class)
.registerType(LinkedHashSet.class)
.registerType(LinkedHashMap.class)
.registerType(Instant.class);
}
}
I use my own permissions, producing SimpleGrantedAuthority, so that type is optional in case you use the default OAuth2UserAuthority / OidcUserAuthority.