diff --git a/.gitignore b/.gitignore index 07ace14a1cb..91b19aa1d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ atlassian-ide-plugin.xml s101plugin.state .attach_pid* .~lock.*# +.kotlin/ !.idea/checkstyle-idea.xml !.idea/externalDependencies.xml diff --git a/cas/spring-security-cas.gradle b/cas/spring-security-cas.gradle index a4750130579..23c5f04a560 100644 --- a/cas/spring-security-cas.gradle +++ b/cas/spring-security-cas.gradle @@ -15,6 +15,7 @@ dependencies { api 'org.springframework:spring-web' optional 'com.fasterxml.jackson.core:jackson-databind' + optional 'tools.jackson.core:jackson-databind' provided 'jakarta.servlet:jakarta.servlet-api' diff --git a/cas/src/main/java/org/springframework/security/cas/jackson/AssertionImplMixin.java b/cas/src/main/java/org/springframework/security/cas/jackson/AssertionImplMixin.java new file mode 100644 index 00000000000..4cd975ec9dd --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/jackson/AssertionImplMixin.java @@ -0,0 +1,71 @@ +/* + * 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.cas.jackson; + +import java.util.Date; +import java.util.Map; + +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.apereo.cas.client.authentication.AttributePrincipal; + +/** + * Helps in jackson deserialization of class + * {@link org.apereo.cas.client.validation.AssertionImpl}, which is used with + * {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. To use + * this class we need to register with {@link tools.jackson.databind.json.JsonMapper}. + * Type information will be stored in @class property. + *
+ *
+ * JsonMapper mapper = JsonMapper.builder() + * .addModule(new CasJacksonModule()) + * .build(); + *+ * + * @author Sebastien Deleuze + * @author Jitendra Singh + * @since 7.0 + * @see CasJacksonModule + * @see org.springframework.security.jackson.SecurityJacksonModules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +class AssertionImplMixin { + + /** + * Mixin Constructor helps in deserialize + * {@link org.apereo.cas.client.validation.AssertionImpl} + * @param principal the Principal to associate with the Assertion. + * @param validFromDate when the assertion is valid from. + * @param validUntilDate when the assertion is valid to. + * @param authenticationDate when the assertion is authenticated. + * @param attributes the key/value pairs for this attribute. + */ + @JsonCreator + AssertionImplMixin( + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonProperty("principal") AttributePrincipal principal, + @JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate, + @JsonProperty("authenticationDate") Date authenticationDate, + @JsonProperty("attributes") Map
+ *
+ * JsonMapper mapper = JsonMapper.builder() + * .addModule(new CasJacksonModule()) + * .build(); + *+ * + * @author Sebastien Deleuze + * @author Jitendra Singh + * @since 7.0 + * @see CasJacksonModule + * @see org.springframework.security.jackson.SecurityJacksonModules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +class AttributePrincipalImplMixin { + + /** + * Mixin Constructor helps in deserialize + * {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} + * @param name the unique identifier for the principal. + * @param attributes the key/value pairs for this principal. + * @param proxyGrantingTicket the ticket associated with this principal. + * @param proxyRetriever the ProxyRetriever implementation to call back to the CAS + * server. + */ + @JsonCreator + AttributePrincipalImplMixin(@JsonProperty("name") String name, + @JsonProperty("attributes") Map
+ * + *
+ * JsonMapper mapper = JsonMapper.builder() + * .addModule(new CasJacksonModule()) + * .build(); + *+ * + * @author Sebastien Deleuze + * @author Jitendra Singh + * @since 7.0 + * @see CasJacksonModule + * @see org.springframework.security.jackson.SecurityJacksonModules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) +@JsonIgnoreProperties(ignoreUnknown = true) +class CasAuthenticationTokenMixin { + + /** + * Mixin Constructor helps in deserialize {@link CasAuthenticationToken} + * @param keyHash hashCode of provided key to identify if this object made by a given + * {@link CasAuthenticationProvider} + * @param principal typically the UserDetails object (cannot be
null
)
+ * @param credentials the service/proxy ticket ID from CAS (cannot be
+ * null
)
+ * @param authorities the authorities granted to the user (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param userDetails the user details (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param assertion the assertion returned from the CAS servers. It contains the
+ * principal and how to obtain a proxy ticket for the user.
+ */
+ @JsonCreator
+ CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonProperty("principal") Object principal,
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonProperty("credentials") Object credentials,
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.CLASS) @JsonProperty("authorities") Collection extends GrantedAuthority> authorities,
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonProperty("userDetails") UserDetails userDetails,
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonProperty("assertion") Assertion assertion) {
+ }
+
+}
diff --git a/cas/src/main/java/org/springframework/security/cas/jackson/CasJacksonModule.java b/cas/src/main/java/org/springframework/security/cas/jackson/CasJacksonModule.java
new file mode 100644
index 00000000000..f96381725d6
--- /dev/null
+++ b/cas/src/main/java/org/springframework/security/cas/jackson/CasJacksonModule.java
@@ -0,0 +1,69 @@
+/*
+ * 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.cas.jackson;
+
+import org.apereo.cas.client.authentication.AttributePrincipalImpl;
+import org.apereo.cas.client.validation.AssertionImpl;
+import tools.jackson.core.Version;
+import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
+
+import org.springframework.security.cas.authentication.CasAuthenticationToken;
+import org.springframework.security.jackson.SecurityJacksonModule;
+import org.springframework.security.jackson.SecurityJacksonModules;
+
+/**
+ * Jackson module for spring-security-cas. This module register
+ * {@link AssertionImplMixin}, {@link AttributePrincipalImplMixin} and
+ * {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then it'll
+ * enable it because typing info is needed to properly serialize/deserialize objects. In
+ * order to use this module just add this module into your JsonMapper configuration.
+ *
+ * + * JsonMapper mapper = JsonMapper.builder() + * .addModule(new CasJacksonModule()) + * .build(); + *+ * + * Note: use {@link SecurityJacksonModules#getModules(ClassLoader)} to get list of all + * security modules on the classpath. + * + * @author Sebastien Deleuze + * @author Jitendra Singh + * @since 7.0 + * @see SecurityJacksonModules + */ +public class CasJacksonModule extends SecurityJacksonModule { + + public CasJacksonModule() { + super(CasJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null)); + } + + @Override + protected void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) { + builder.allowIfSubType(AssertionImpl.class) + .allowIfSubType(AttributePrincipalImpl.class) + .allowIfSubType(CasAuthenticationToken.class); + } + + @Override + public void setupModule(SetupContext context) { + context.setMixIn(AssertionImpl.class, AssertionImplMixin.class); + context.setMixIn(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class); + context.setMixIn(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class); + } + +} diff --git a/cas/src/main/java/org/springframework/security/cas/jackson/package-info.java b/cas/src/main/java/org/springframework/security/cas/jackson/package-info.java new file mode 100644 index 00000000000..261d47c9b5b --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/jackson/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Jackson 3+ serialization support for CAS. + */ +package org.springframework.security.cas.jackson; diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java b/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java index 66fd0c77d53..115aead48c0 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java @@ -33,6 +33,7 @@ * this class we need to register with * {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information will be stored * in @class property. + * *
*
* ObjectMapper mapper = new ObjectMapper(); @@ -43,7 +44,10 @@ * @since 4.2 * @see CasJackson2Module * @see org.springframework.security.jackson2.SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.cas.jackson.AssertionImplMixin} based on Jackson 3 */ +@Deprecated(forRemoval = true) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/AttributePrincipalImplMixin.java b/cas/src/main/java/org/springframework/security/cas/jackson2/AttributePrincipalImplMixin.java index 591aea37f6d..b786a505004 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/AttributePrincipalImplMixin.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/AttributePrincipalImplMixin.java @@ -30,6 +30,7 @@ * {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with * {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type * information will be stored in property named @class. + * ** - * @author Jitendra Singh. + * @author Jitendra Singh * @since 4.2 + * @deprecated as of 7.0 in favor of + * {@link org.springframework.security.jackson.SecurityJacksonModules} based on Jackson 3 */ +@Deprecated(forRemoval = true) public final class SecurityJackson2Modules { private static final Log logger = LogFactory.getLog(SecurityJackson2Modules.class); diff --git a/core/src/main/java/org/springframework/security/jackson2/SimpleGrantedAuthorityMixin.java b/core/src/main/java/org/springframework/security/jackson2/SimpleGrantedAuthorityMixin.java index 4424e6fca44..644f2092f81 100644 --- a/core/src/main/java/org/springframework/security/jackson2/SimpleGrantedAuthorityMixin.java +++ b/core/src/main/java/org/springframework/security/jackson2/SimpleGrantedAuthorityMixin.java @@ -35,11 +35,15 @@ * @since 4.2 * @see CoreJackson2Module * @see SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.jackson.SimpleGrantedAuthorityMixin} based on + * Jackson 3 */ @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) +@Deprecated(forRemoval = true) public abstract class SimpleGrantedAuthorityMixin { /** diff --git a/core/src/main/java/org/springframework/security/jackson2/UnmodifiableListDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableListDeserializer.java index bd62265c7de..ecc28aea190 100644 --- a/core/src/main/java/org/springframework/security/jackson2/UnmodifiableListDeserializer.java +++ b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableListDeserializer.java @@ -28,7 +28,12 @@ * @author Hyunmin Choi * @since 5.0.2 * @see UnmodifiableListMixin + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.jackson.AbstractUnmodifiableCollectionDeserializer} + * based on Jackson 3 */ +@SuppressWarnings("removal") +@Deprecated(forRemoval = true) class UnmodifiableListDeserializer extends AbstractUnmodifiableCollectionDeserializer*
* ObjectMapper mapper = new ObjectMapper(); @@ -40,7 +41,11 @@ * @since 4.2 * @see CasJackson2Module * @see org.springframework.security.jackson2.SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.cas.jackson.AttributePrincipalImplMixin} based on + * Jackson 3 */ +@Deprecated(forRemoval = true) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixin.java b/cas/src/main/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixin.java index dd44c8e2851..5677285ae36 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixin.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixin.java @@ -40,7 +40,6 @@ * * *Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list * of all security modules. * - * @author Jitendra Singh. + * @author Jitendra Singh * @since 4.2 * @see SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@link org.springframework.security.jackson.CoreJacksonModule} based on Jackson 3 */ -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "removal" }) +@Deprecated(forRemoval = true) public class CoreJackson2Module extends SimpleModule { public CoreJackson2Module() { diff --git a/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java b/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java index b69fe113933..bc9d38aa9d3 100644 --- a/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java +++ b/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java @@ -50,11 +50,15 @@ * @since 4.2 * @see CoreJackson2Module * @see SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.jackson.RememberMeAuthenticationTokenMixin} based + * on Jackson 3 */ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) @JsonIgnoreProperties(ignoreUnknown = true) +@Deprecated(forRemoval = true) class RememberMeAuthenticationTokenMixin { /** diff --git a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java index 50720289739..d9b16e997d0 100644 --- a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java +++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java @@ -67,9 +67,12 @@ * mapper.registerModule(new Saml2Jackson2Module()); *- * *
* ObjectMapper mapper = new ObjectMapper(); * mapper.registerModule(new CasJackson2Module()); @@ -50,7 +49,11 @@ * @since 4.2 * @see CasJackson2Module * @see org.springframework.security.jackson2.SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@code org.springframework.security.cas.jackson.CasAuthenticationTokenMixin} based on + * Jackson 3 */ +@Deprecated(forRemoval = true) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java index 8acf7e2e968..f04b18ce8da 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java @@ -37,11 +37,14 @@ *Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list * of all security modules on the classpath. * - * @author Jitendra Singh. + * @author Jitendra Singh * @since 4.2 * @see org.springframework.security.jackson2.SecurityJackson2Modules + * @deprecated as of 7.0 in favor of + * {@link org.springframework.security.cas.jackson.CasJacksonModule} based on Jackson 3 */ -@SuppressWarnings("serial") +@Deprecated(forRemoval = true) +@SuppressWarnings({ "serial", "removal" }) public class CasJackson2Module extends SimpleModule { public CasJackson2Module() { diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java b/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java index dae1c1502db..86000c1c2c2 100644 --- a/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java @@ -15,7 +15,7 @@ */ /** - * Jackson support for CAS. + * Jackson 2 support for CAS. */ @NullMarked package org.springframework.security.cas.jackson2; diff --git a/cas/src/test/java/org/springframework/security/cas/jackson/CasAuthenticationTokenMixinTests.java b/cas/src/test/java/org/springframework/security/cas/jackson/CasAuthenticationTokenMixinTests.java new file mode 100644 index 00000000000..baf14b43da6 --- /dev/null +++ b/cas/src/test/java/org/springframework/security/cas/jackson/CasAuthenticationTokenMixinTests.java @@ -0,0 +1,146 @@ +/* + * 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.cas.jackson; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +import org.apereo.cas.client.authentication.AttributePrincipalImpl; +import org.apereo.cas.client.validation.Assertion; +import org.apereo.cas.client.validation.AssertionImpl; +import org.json.JSONException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import tools.jackson.databind.json.JsonMapper; + +import org.springframework.security.cas.authentication.CasAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.jackson.SecurityJacksonModules; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jitendra Singh + * @since 4.2 + */ +public class CasAuthenticationTokenMixinTests { + + private static final String KEY = "casKey"; + + private static final String PASSWORD = "\"1234\""; + + private static final Date START_DATE = new Date(); + + private static final Date END_DATE = new Date(); + + public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}"; + + public static final String AUTHORITIES_SET_JSON = "[" + AUTHORITY_JSON + "]"; + + public static final String AUTHORITIES_ARRAYLIST_JSON = "[" + AUTHORITY_JSON + "]"; + + // @formatter:off + public static final String USER_JSON = "{" + + "\"@class\": \"org.springframework.security.core.userdetails.User\", " + + "\"username\": \"admin\"," + + " \"password\": " + PASSWORD + ", " + + "\"accountNonExpired\": true, " + + "\"accountNonLocked\": true, " + + "\"credentialsNonExpired\": true, " + + "\"enabled\": true, " + + "\"authorities\": " + AUTHORITIES_SET_JSON + + "}"; + // @formatter:on + private static final String CAS_TOKEN_JSON = "{" + + "\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", " + + "\"keyHash\": " + KEY.hashCode() + "," + "\"principal\": " + USER_JSON + ", " + "\"credentials\": " + + PASSWORD + ", " + "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + "," + "\"userDetails\": " + USER_JSON + + "," + "\"authenticated\": true, " + "\"details\": null," + "\"assertion\": {" + + "\"@class\": \"org.apereo.cas.client.validation.AssertionImpl\", " + "\"principal\": {" + + "\"@class\": \"org.apereo.cas.client.authentication.AttributePrincipalImpl\", " + + "\"name\": \"assertName\", " + "\"attributes\": {}, " + "\"proxyGrantingTicket\": null, " + + "\"proxyRetriever\": null" + "}, " + "\"validFromDate\":\"" + START_DATE.toInstant() + "\", " + + "\"validUntilDate\":\"" + END_DATE.toInstant() + "\"," + "\"authenticationDate\":\"" + + START_DATE.toInstant() + "\", " + "\"attributes\": {}," + "\"context\": {}" + "}" + "}"; + + private static final String CAS_TOKEN_CLEARED_JSON = CAS_TOKEN_JSON.replaceFirst(PASSWORD, "null"); + + protected JsonMapper mapper; + + @BeforeEach + public void setup() { + ClassLoader loader = getClass().getClassLoader(); + this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build(); + } + + @Test + public void serializeCasAuthenticationTest() throws JSONException { + CasAuthenticationToken token = createCasAuthenticationToken(); + String actualJson = this.mapper.writeValueAsString(token); + JSONAssert.assertEquals(CAS_TOKEN_JSON, actualJson, true); + } + + @Test + public void serializeCasAuthenticationTestAfterEraseCredentialInvoked() throws JSONException { + CasAuthenticationToken token = createCasAuthenticationToken(); + token.eraseCredentials(); + String actualJson = this.mapper.writeValueAsString(token); + JSONAssert.assertEquals(CAS_TOKEN_CLEARED_JSON, actualJson, true); + } + + @Test + public void deserializeCasAuthenticationTestAfterEraseCredentialInvoked() { + CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_CLEARED_JSON, CasAuthenticationToken.class); + assertThat(((UserDetails) token.getPrincipal()).getPassword()).isNull(); + } + + @Test + public void deserializeCasAuthenticationTest() throws IOException { + CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_JSON, CasAuthenticationToken.class); + assertThat(token).isNotNull(); + assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class); + assertThat(((User) token.getPrincipal()).getUsername()).isEqualTo("admin"); + assertThat(((User) token.getPrincipal()).getPassword()).isEqualTo("1234"); + assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class); + assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class); + assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode()); + assertThat(token.getUserDetails().getAuthorities()).extracting(GrantedAuthority::getAuthority) + .containsOnly("ROLE_USER"); + assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE); + assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE); + assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE); + assertThat(token.getAssertion().getPrincipal().getName()).isEqualTo("assertName"); + assertThat(token.getAssertion().getAttributes()).hasSize(0); + } + + private CasAuthenticationToken createCasAuthenticationToken() { + User principal = new User("admin", "1234", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); + Collection extends GrantedAuthority> authorities = Collections + .singletonList(new SimpleGrantedAuthority("ROLE_USER")); + Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), START_DATE, END_DATE, + START_DATE, Collections.emptyMap()); + return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities, + new User("admin", "1234", authorities), assertion); + } + +} diff --git a/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java b/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java index 98ddf8a4bc0..85aaeb211c1 100644 --- a/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java +++ b/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java @@ -44,6 +44,7 @@ * @author Jitendra Singh * @since 4.2 */ +@SuppressWarnings("removal") public class CasAuthenticationTokenMixinTests { private static final String KEY = "casKey"; diff --git a/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java b/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java index 8a85a37182c..d8a68517956 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java +++ b/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java @@ -19,8 +19,10 @@ import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.http.converter.GenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** @@ -32,6 +34,8 @@ */ final class HttpMessageConverters { + private static final boolean jacksonPresent; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -40,6 +44,7 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -50,6 +55,9 @@ private HttpMessageConverters() { } static GenericHttpMessageConverter