From 18804a9090d54ed0964c96f0e3eefea7fd0eb17a Mon Sep 17 00:00:00 2001 From: Marcus Hert da Coregio Date: Tue, 21 Oct 2025 22:54:59 -0300 Subject: [PATCH] Create Jackson Mixin for OneTimeTokenAuthenticationToken Closes gh-18095 Signed-off-by: Marcus Hert da Coregio --- .../security/jackson2/CoreJackson2Module.java | 2 + .../OneTimeTokenAuthenticationTokenMixin.java | 55 +++++++++++++++++++ .../OneTimeTokenAuthenticationTokenTests.java | 46 ++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 core/src/main/java/org/springframework/security/jackson2/OneTimeTokenAuthenticationTokenMixin.java create mode 100644 core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTokenTests.java diff --git a/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java b/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java index 844c8ecdd0e..280c2352344 100644 --- a/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java +++ b/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java @@ -25,6 +25,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; @@ -70,6 +71,7 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class); context.setMixInAnnotations(BadCredentialsException.class, BadCredentialsExceptionMixin.class); + context.setMixInAnnotations(OneTimeTokenAuthenticationToken.class, OneTimeTokenAuthenticationTokenMixin.class); } } diff --git a/core/src/main/java/org/springframework/security/jackson2/OneTimeTokenAuthenticationTokenMixin.java b/core/src/main/java/org/springframework/security/jackson2/OneTimeTokenAuthenticationTokenMixin.java new file mode 100644 index 00000000000..85fec78a331 --- /dev/null +++ b/core/src/main/java/org/springframework/security/jackson2/OneTimeTokenAuthenticationTokenMixin.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.jackson2; + +import java.util.Collection; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.springframework.security.core.GrantedAuthority; + +/** + * Jackson Mixin class helps in serialize/deserialize + * {@link org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken}. + * + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new CoreJackson2Module());
+ * 
+ * + * @author Marcus Da Coregio + * @since 6.5.7 + * @see CoreJackson2Module + * @see SecurityJackson2Modules + * + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class OneTimeTokenAuthenticationTokenMixin { + + @JsonCreator + OneTimeTokenAuthenticationTokenMixin(@JsonProperty("principal") Object principal, + @JsonProperty("authorities") Collection authorities) { + } + +} diff --git a/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTokenTests.java new file mode 100644 index 00000000000..9e1522ab26a --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTokenTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication.ott; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.jackson2.SecurityJackson2Modules; + +import static org.assertj.core.api.Assertions.assertThat; + +class OneTimeTokenAuthenticationTokenTests { + + // gh-18095 + @Test + void shouldBeAbleToDeserializeFromJsonWithDefaultTypingActivated() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader())); + OneTimeTokenAuthenticationToken oneTimeTokenAuthenticationToken = new OneTimeTokenAuthenticationToken( + "principal", AuthorityUtils.createAuthorityList("ROLE_USER")); + byte[] serialized = mapper.writeValueAsBytes(oneTimeTokenAuthenticationToken); + OneTimeTokenAuthenticationToken deserialized = mapper.readValue(serialized, + OneTimeTokenAuthenticationToken.class); + assertThat(deserialized.getPrincipal()).isEqualTo(oneTimeTokenAuthenticationToken.getPrincipal()); + assertThat(deserialized.getAuthorities()).extracting(GrantedAuthority::getAuthority).contains("ROLE_USER"); + } + +}