Skip to content

Problem while Deserializing java.timeDuration.class with org.springframework.security.oauth2.server.authorization.jackson2.DurationMixin with JdbcRegisteredClientRepository #1984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Ibanezos92 opened this issue Apr 19, 2025 · 0 comments
Labels
type: bug A general bug

Comments

@Ibanezos92
Copy link

Ibanezos92 commented Apr 19, 2025

Describe the bug
Deserializing a Duration.class instance stored as JSON in oauth2_registered_client table in the token_settings column doesn't use the registered mixin of org.springframework.security.oauth2.server.authorization.jackson2.DurationMixin annotated with

@JsonCreator
	static void ofSeconds(@JsonProperty("seconds") long seconds, @JsonProperty("nano") long nanoAdjustment) {}

but the:
com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer
registered from the:
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
resulting in the following exception:

java.lang.IllegalArgumentException: Unexpected token (FIELD_NAME), expected one of [VALUE_STRING, VALUE_NUMBER_INT, VALUE_NUMBER_FLOAT] for java.time.Duration value
 at [Source: (String)"{"@class":"java.util.HashMap","settings.token.access-token-time-to-live":{"@class":"java.time.Duration","seconds":7200}}"; line: 1, column: 105] (through reference chain: java.util.HashMap["settings.token.access-token-time-to-live"])

Because the token_settings are stored as a JSON in the database, and are deserialized in runtime using
JdbcRegisteredClientRepository's parseMap:

return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {});

as far as I understand the stored JSON value must declared its' class types like this for the Generic deserializer to work:

{
    "@class": "java.util.HashMap",
    "settings.token.access-token-time-to-live": {
        "@class": "java.time.Duration",
        "seconds": 7200
    }
}

If the above statement is wrong, then this whole issue is non existing, and I'm just not persisting the information correct to the database.

To Reproduce
I am using PostgresSQL and I am attaching my flyway db migration files for the schema I am using, grabbed it from the official documentation.
Register a jdbc client with the client credential flow and a token setting of access token expiration:

INSERT INTO public.oauth2_registered_client (id, client_id, client_id_issued_at, client_secret, client_secret_expires_at, client_name, client_authentication_methods, authorization_grant_types, redirect_uris, post_logout_redirect_uris, scopes, client_settings, token_settings) VALUES ('e25c0e15-2023-4e91-8121-b8822c40382d', 'template_oauth_client', '2025-04-19 10:57:49.440316', '{bcrypt}$2a$10$cB44Cftb9WUN9Pu37e0ZFOsKleE2wbCfTT2w8Qry8AOBYHldWbEOe', null, 'Template Client', 'client_secret_post,client_secret_basic', 'client_credentials', '', '', 'openid,profile,message.read,message.write', '{"@class":"java.util.HashMap","clientCode":"182"}', '{"@class":"java.util.HashMap","settings.token.access-token-time-to-live":{"@class":"java.time.Duration","seconds":7200}}');

Try to issue a token from the authorization server:

curl --location 'http://localhost:9000/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=template_oauth_client' \
--data-urlencode 'client_secret=template_secret' \
--data-urlencode 'grant_type=client_credentials'

Expected behavior
I am expecting to get a success 200 Resposne and an access token which will be valid for 7200 seconds, but I am getting a response of 302 Found and prompt for login, because internally there was an error which was not handled properly.
There is not login also in the client_credentials flow.

Sample
You can recreate the problem by pulling:
https://github.com/Ibanezos92/spring_authorization_server_deserialization_problem
You need a postgres that you can create with docker:

docker run -e POSTGRES_PASSWORD=pass123 -p 5432:5432 -d -v /Users/home/Documents/psql/data:/var/lib/postgresql/data postgres

I have created a fix, that I am registering a custom deserializing as I think that the mixin is the problem. Maybe because the java.time.Duration is a final class, those mixin annotations cannot be applied, so it reverts to the registered com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer.
Also the JsonCreator of org.springframework.security.oauth2.server.authorization.jackson2.DurationMixin does not return a java.time.Duration object but a void, which it seems wrong (or maybe I am missing something).

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
		isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE,
		creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class DurationMixin {

	@JsonCreator
	static void ofSeconds(@JsonProperty("seconds") long seconds, @JsonProperty("nano") long nanoAdjustment) {
	}

	@JsonGetter("seconds")
	abstract long getSeconds();

	@JsonGetter("nano")
	abstract int getNano();

}

To see a working solution Instead of using the problematic:
com.example.AuthServerConfig_Problem
which replicates the problem, you may use:
com.example.AuthServerConfig_CustomFix
by commenting/uncommenting the @configuration.
which uses my custom registered Duration Deserializer.

The repository uses Groovy instead of Java.

@Ibanezos92 Ibanezos92 added the type: bug A general bug label Apr 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

1 participant