Add Jackson 3 support

This commit adds support for Jackson 3 which has the following
major differences with the Jackson 2 one:
 - jackson subpackage instead of jackson2
 - Jackson type prefix instead of Jackson2
 - JsonMapper instead of ObjectMapper
 - For configuration, JsonMapper.Builder instead of ObjectMapper
   since the latter is now immutable
 - Remove custom support for unmodifiable collections
 - Use safe default typing via a PolymorphicTypeValidator

Jackson 3 changes compared to Jackson 2 are documented in
https://cowtowncoder.medium.com/jackson-3-0-0-ga-released-1f669cda529a
and
https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md.

This commit does not cover webauthn which is a special case (uses
jackson sub-package for Jackson 2 support) which will be handled in
a distinct commit.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
This commit is contained in:
Sébastien Deleuze 2025-09-01 18:23:31 +02:00 committed by Rob Winch
parent 916a687b29
commit 65a14d6c6d
156 changed files with 9052 additions and 146 deletions

View File

@ -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'

View File

@ -0,0 +1,60 @@
/*
* 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.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}.
*
* @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)
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(@JsonProperty("principal") AttributePrincipal principal,
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
@JsonProperty("authenticationDate") Date authenticationDate,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apereo.cas.client.proxy.ProxyRetriever;
/**
* Helps in deserialize
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
*
* @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)
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<String, Object> attributes,
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
}
}

View File

@ -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 java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apereo.cas.client.validation.Assertion;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* Mixin class which helps in deserialize {@link CasAuthenticationToken} using jackson.
*
* @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)
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 <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param userDetails the user details (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @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, @JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
}
}

View File

@ -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 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.
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @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
public 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);
}
}

View File

@ -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;

View File

@ -15,7 +15,7 @@
*/
/**
* Jackson support for CAS.
* Jackson 2 support for CAS.
*/
@NullMarked
package org.springframework.security.cas.jackson2;

View File

@ -0,0 +1,151 @@
/*
* 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 = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON
+ "]]";
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", ["
+ 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\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
+ "\"proxyGrantingTicket\": null, " + "\"proxyRetriever\": null" + "}, "
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"},"
+ "\"context\": {\"@class\":\"java.util.HashMap\"}" + "}" + "}";
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.<String, Object>emptyMap());
return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities,
new User("admin", "1234", authorities), assertion);
}
}

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -167,7 +168,8 @@ public class JwkSetTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -176,7 +178,8 @@ public class JwkSetTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -46,6 +46,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -1300,7 +1301,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -1309,7 +1311,8 @@ public class OAuth2AuthorizationCodeGrantTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -40,6 +40,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -573,7 +574,8 @@ public class OAuth2ClientCredentialsGrantTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -582,7 +584,8 @@ public class OAuth2ClientCredentialsGrantTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -39,6 +39,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -516,7 +517,8 @@ public class OAuth2RefreshTokenGrantTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -525,7 +527,8 @@ public class OAuth2RefreshTokenGrantTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -553,7 +554,8 @@ public class OAuth2TokenIntrospectionTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -562,7 +564,8 @@ public class OAuth2TokenIntrospectionTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -351,7 +352,8 @@ public class OAuth2TokenRevocationTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -360,7 +362,8 @@ public class OAuth2TokenRevocationTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -695,7 +696,8 @@ public class OidcTests {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}
@ -704,7 +706,8 @@ public class OidcTests {
ParametersMapper() {
super();
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
setMapper(new JdbcOAuth2AuthorizationService.JacksonDelegate(JsonMapper.builder()
.addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class)));
}
}

View File

@ -25,6 +25,7 @@ dependencies {
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-tx'
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
optional 'tools.jackson.core:jackson-databind'
testImplementation 'commons-collections:commons-collections'
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

View File

@ -0,0 +1,57 @@
/*
* 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.jackson;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
/**
* This is a Jackson mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken} class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
class AnonymousAuthenticationTokenMixin {
/**
* Constructor used by Jackson to create object of
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken}.
* @param keyHash hashCode of key provided at the time of token creation by using
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken#AnonymousAuthenticationToken(String, Object, Collection)}
* @param principal the principal (typically a <code>UserDetails</code>)
* @param authorities the authorities granted to the principal
*/
@JsonCreator
AnonymousAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}

View File

@ -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.jackson;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.BadCredentialsException} class.
*
* @author Sebastien Deleuze
* @author Yannick Lombardi
* @since 7.0
* @see CoreJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonIgnoreProperties({ "cause", "stackTrace", "authenticationRequest" })
class BadCredentialsExceptionMixin {
/**
* Constructor used by Jackson to create
* {@link org.springframework.security.authentication.BadCredentialsException} object.
* @param message the detail message
*/
@JsonCreator
BadCredentialsExceptionMixin(@JsonProperty("message") String message) {
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.jackson;
import java.time.Duration;
import java.time.Instant;
import tools.jackson.core.Version;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
/**
* Jackson module for spring-security-core. This module register
* {@link AnonymousAuthenticationTokenMixin}, {@link RememberMeAuthenticationTokenMixin},
* {@link SimpleGrantedAuthorityMixin}, {@link FactorGrantedAuthorityMixin},
* {{@link UserMixin}, {@link UsernamePasswordAuthenticationTokenMixin} and
* {@link UsernamePasswordAuthenticationTokenMixin}.
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.O
* @see SecurityJacksonModules
*/
@SuppressWarnings("serial")
public class CoreJacksonModule extends SecurityJacksonModule {
public CoreJacksonModule() {
super(CoreJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
protected CoreJacksonModule(String name, Version version) {
super(name, version);
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(Instant.class)
.allowIfSubType(Duration.class)
.allowIfSubType(SimpleGrantedAuthority.class)
.allowIfSubType(FactorGrantedAuthority.class)
.allowIfSubType(UsernamePasswordAuthenticationToken.class)
.allowIfSubType(RememberMeAuthenticationToken.class)
.allowIfSubType(AnonymousAuthenticationToken.class)
.allowIfSubType(User.class)
.allowIfSubType(BadCredentialsException.class)
.allowIfSubType(SecurityContextImpl.class)
.allowIfSubType(TestingAuthenticationToken.class)
.allowIfSubType("java.util.Collections$UnmodifiableSet")
.allowIfSubType("java.util.Collections$UnmodifiableRandomAccessList")
.allowIfSubType("java.util.Collections$EmptyList")
.allowIfSubType("java.util.ArrayList")
.allowIfSubType("java.util.HashMap")
.allowIfSubType("java.util.Collections$EmptyMap")
.allowIfSubType("java.util.Date")
.allowIfSubType("java.util.Arrays$ArrayList")
.allowIfSubType("java.util.Collections$UnmodifiableMap")
.allowIfSubType("java.util.LinkedHashMap")
.allowIfSubType("java.util.Collections$SingletonList")
.allowIfSubType("java.util.TreeMap")
.allowIfSubType("java.util.HashSet")
.allowIfSubType("java.util.LinkedHashSet");
}
@Override
public void setupModule(SetupContext context) {
((MapperBuilder<?, ?>) context.getOwner()).enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS);
context.setMixIn(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class);
context.setMixIn(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);
context.setMixIn(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
context.setMixIn(FactorGrantedAuthority.class, FactorGrantedAuthorityMixin.class);
context.setMixIn(User.class, UserMixin.class);
context.setMixIn(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class);
context.setMixIn(BadCredentialsException.class, BadCredentialsExceptionMixin.class);
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.jackson;
import java.time.Instant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson Mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.authority.SimpleGrantedAuthority}.
*
* @author Sebastien Deleuze
* @author Rob Winch
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class FactorGrantedAuthorityMixin {
/**
* Mixin Constructor.
* @param authority the authority
*/
@JsonCreator
FactorGrantedAuthorityMixin(@JsonProperty("authority") String authority,
@JsonProperty("issuedAt") Instant issuedAt) {
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.jackson;
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;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.authentication.RememberMeAuthenticationToken}
* class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@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)
class RememberMeAuthenticationTokenMixin {
/**
* Constructor used by Jackson to create
* {@link org.springframework.security.authentication.RememberMeAuthenticationToken}
* object.
* @param keyHash hashCode of above given key.
* @param principal the principal (typically a <code>UserDetails</code>)
* @param authorities the authorities granted to the principal
*/
@JsonCreator
RememberMeAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.jackson;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
import tools.jackson.databind.module.SimpleModule;
/**
* Jackson module allowing to contribute {@link PolymorphicTypeValidator} configuration.
*
* @author Sebastien Deleuze
* @since 7.0
*/
public abstract class SecurityJacksonModule extends SimpleModule {
public SecurityJacksonModule() {
super();
}
public SecurityJacksonModule(String name, Version version) {
super(name, version, null);
}
public abstract void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder);
}

View File

@ -0,0 +1,203 @@
/*
* 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.jackson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.DefaultTyping;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
import tools.jackson.databind.module.SimpleModule;
import org.springframework.core.log.LogMessage;
import org.springframework.util.ClassUtils;
/**
* This utility class will find all the Jackson modules contributed by Spring Security in
* the classpath (except {@code OAuth2AuthorizationServerJacksonModule} and
* {@code WebauthnJacksonModule}), enable automatic inclusion of type information and
* configure a {@link PolymorphicTypeValidator} that handles the validation of class
* names.
*
* <p>
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* If needed, you can add custom classes to the validation handling.
* <p>
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder()
* .allowIfSubType(MyCustomType.class);
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader, builder))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
*/
public final class SecurityJacksonModules {
private static final Log logger = LogFactory.getLog(SecurityJacksonModules.class);
private static final List<String> securityJacksonModuleClasses = Arrays.asList(
"org.springframework.security.jackson.CoreJacksonModule",
"org.springframework.security.web.jackson.WebJacksonModule",
"org.springframework.security.web.server.jackson.WebServerJacksonModule");
private static final String webServletJacksonModuleClass = "org.springframework.security.web.jackson.WebServletJacksonModule";
private static final String oauth2ClientJacksonModuleClass = "org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule";
private static final String ldapJacksonModuleClass = "org.springframework.security.ldap.jackson.LdapJacksonModule";
private static final String saml2JacksonModuleClass = "org.springframework.security.saml2.jackson.Saml2JacksonModule";
private static final String casJacksonModuleClass = "org.springframework.security.cas.jackson.CasJacksonModule";
private static final boolean webServletPresent;
private static final boolean oauth2ClientPresent;
private static final boolean ldapJacksonPresent;
private static final boolean saml2JacksonPresent;
private static final boolean casJacksonPresent;
static {
ClassLoader classLoader = SecurityJacksonModules.class.getClassLoader();
webServletPresent = ClassUtils.isPresent("jakarta.servlet.http.Cookie", classLoader);
oauth2ClientPresent = ClassUtils.isPresent("org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
classLoader);
ldapJacksonPresent = ClassUtils.isPresent(ldapJacksonModuleClass, classLoader);
saml2JacksonPresent = ClassUtils.isPresent(saml2JacksonModuleClass, classLoader);
casJacksonPresent = ClassUtils.isPresent(casJacksonModuleClass, classLoader);
}
private SecurityJacksonModules() {
}
@SuppressWarnings("unchecked")
private static @Nullable SecurityJacksonModule loadAndGetInstance(String className, ClassLoader loader) {
try {
Class<? extends SecurityJacksonModule> securityModule = (Class<? extends SecurityJacksonModule>) ClassUtils
.forName(className, loader);
logger.debug(LogMessage.format("Loaded module %s, now registering", className));
return securityModule.getConstructor().newInstance();
}
catch (Exception ex) {
logger.debug(LogMessage.format("Cannot load module %s", className), ex);
}
return null;
}
/**
* Return the list of available security modules in classpath, enable automatic
* inclusion of type information and configure a default
* {@link PolymorphicTypeValidator} that handles the validation of class names.
* @param loader the ClassLoader to use
* @return List of available security modules in classpath
* @see #getModules(ClassLoader, BasicPolymorphicTypeValidator.Builder)
*/
public static List<JacksonModule> getModules(ClassLoader loader) {
return getModules(loader, null);
}
/**
* Return the list of available security modules in classpath, enable automatic
* inclusion of type information and configure a default
* {@link PolymorphicTypeValidator} customizable with the provided builder that
* handles the validation of class names.
* @param loader the ClassLoader to use
* @param typeValidatorBuilder the builder to configure custom types allowed in
* addition to Spring Security ones
* @return List of available security modules in classpath.
*/
public static List<JacksonModule> getModules(ClassLoader loader,
BasicPolymorphicTypeValidator.@Nullable Builder typeValidatorBuilder) {
List<JacksonModule> modules = new ArrayList<>();
for (String className : securityJacksonModuleClasses) {
addToModulesList(loader, modules, className);
}
if (webServletPresent) {
addToModulesList(loader, modules, webServletJacksonModuleClass);
}
if (oauth2ClientPresent) {
addToModulesList(loader, modules, oauth2ClientJacksonModuleClass);
}
if (ldapJacksonPresent) {
addToModulesList(loader, modules, ldapJacksonModuleClass);
}
if (saml2JacksonPresent) {
addToModulesList(loader, modules, saml2JacksonModuleClass);
}
if (casJacksonPresent) {
addToModulesList(loader, modules, casJacksonModuleClass);
}
applyPolymorphicTypeValidator(modules, typeValidatorBuilder);
return modules;
}
private static void applyPolymorphicTypeValidator(List<JacksonModule> modules,
BasicPolymorphicTypeValidator.@Nullable Builder typeValidatorBuilder) {
BasicPolymorphicTypeValidator.Builder builder = (typeValidatorBuilder != null) ? typeValidatorBuilder
: BasicPolymorphicTypeValidator.builder();
for (JacksonModule module : modules) {
if (module instanceof SecurityJacksonModule securityModule) {
securityModule.configurePolymorphicTypeValidator(builder);
}
}
modules.add(new SimpleModule() {
@Override
public void setupModule(SetupContext context) {
((MapperBuilder<?, ?>) context.getOwner()).activateDefaultTyping(builder.build(),
DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
}
});
}
/**
* @param loader the ClassLoader to use
* @param modules list of the modules to add
* @param className name of the class to instantiate
*/
private static void addToModulesList(ClassLoader loader, List<JacksonModule> modules, String className) {
SecurityJacksonModule module = loadAndGetInstance(className, loader);
if (module != null) {
modules.add(module);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson Mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.authority.SimpleGrantedAuthority}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public abstract class SimpleGrantedAuthorityMixin {
/**
* Mixin Constructor.
* @param role the role
*/
@JsonCreator
public SimpleGrantedAuthorityMixin(@JsonProperty("authority") String role) {
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.jackson;
import java.util.Set;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.node.MissingNode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/**
* Custom Deserializer for {@link User} class. This is already registered with
* {@link UserMixin}. You can also use it directly with your mixin class.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see UserMixin
*/
class UserDeserializer extends ValueDeserializer<User> {
private static final TypeReference<Set<GrantedAuthority>> GRANTED_AUTHORITY_SET = new TypeReference<>() {
};
/**
* This method will create {@link User} object. It will ensure successful object
* creation even if password key is null in serialized json, because credentials may
* be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
* that case there won't be any password key in serialized json.
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws JacksonException if an error during JSON processing occurs
*/
@Override
public User deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException {
JsonNode jsonNode = ctxt.readTree(jp);
JsonNode authoritiesNode = readJsonNode(jsonNode, "authorities");
Set<GrantedAuthority> authorities = ctxt.readTreeAsValue(authoritiesNode,
ctxt.getTypeFactory().constructType(GRANTED_AUTHORITY_SET));
JsonNode passwordNode = readJsonNode(jsonNode, "password");
String username = readJsonNode(jsonNode, "username").asString();
String password = (passwordNode.isMissingNode()) ? null : passwordNode.stringValue();
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
User result = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
authorities);
if (passwordNode.asString(null) == null) {
result.eraseCredentials();
}
return result;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* This mixin class helps in serialize/deserialize
* {@link org.springframework.security.core.userdetails.User}. This class also register a
* custom deserializer {@link UserDeserializer} to deserialize User object successfully.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see UserDeserializer
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = UserDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class UserMixin {
}

View File

@ -0,0 +1,105 @@
/*
* 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.jackson;
import java.util.List;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DatabindException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.node.MissingNode;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* Custom deserializer for {@link UsernamePasswordAuthenticationToken}. At the time of
* deserialization it will invoke suitable constructor depending on the value of
* <b>authenticated</b> property. It will ensure that the token's state must not change.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @author Greg Turnquist
* @author Onur Kagan Ozcan
* @since 7.0
* @see UsernamePasswordAuthenticationTokenMixin
*/
class UsernamePasswordAuthenticationTokenDeserializer extends ValueDeserializer<UsernamePasswordAuthenticationToken> {
private static final TypeReference<List<GrantedAuthority>> GRANTED_AUTHORITY_LIST = new TypeReference<>() {
};
/**
* This method construct {@link UsernamePasswordAuthenticationToken} object from
* serialized json.
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws JacksonException if an error during JSON processing occurs
*/
@Override
public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt)
throws JacksonException {
JsonNode jsonNode = ctxt.readTree(jp);
boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = readJsonNode(jsonNode, "principal");
Object principal = getPrincipal(ctxt, principalNode);
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
Object credentials = getCredentials(credentialsNode);
JsonNode authoritiesNode = readJsonNode(jsonNode, "authorities");
List<GrantedAuthority> authorities = ctxt.readTreeAsValue(authoritiesNode,
ctxt.getTypeFactory().constructType(GRANTED_AUTHORITY_LIST));
UsernamePasswordAuthenticationToken token = (!authenticated)
? UsernamePasswordAuthenticationToken.unauthenticated(principal, credentials)
: UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities);
JsonNode detailsNode = readJsonNode(jsonNode, "details");
if (detailsNode.isNull() || detailsNode.isMissingNode()) {
token.setDetails(null);
}
else {
Object details = ctxt.readTreeAsValue(detailsNode, Object.class);
token.setDetails(details);
}
return token;
}
private @Nullable Object getCredentials(JsonNode credentialsNode) {
if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
return null;
}
return credentialsNode.asString();
}
private Object getPrincipal(DeserializationContext ctxt, JsonNode principalNode)
throws StreamReadException, DatabindException {
if (principalNode.isObject()) {
return ctxt.readTreeAsValue(principalNode, Object.class);
}
return principalNode.asString();
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* This mixin class is used to serialize / deserialize
* {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}.
* This class register a custom deserializer
* {@link UsernamePasswordAuthenticationTokenDeserializer}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CoreJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = UsernamePasswordAuthenticationTokenDeserializer.class)
abstract class UsernamePasswordAuthenticationTokenMixin {
}

View File

@ -0,0 +1,23 @@
/*
* 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.
*/
@NullMarked
package org.springframework.security.jackson;
import org.jspecify.annotations.NullMarked;

View File

@ -71,7 +71,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = readJsonNode(jsonNode, "principal");
Object principal = getPrincipal(mapper, principalNode);
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");

View File

@ -15,10 +15,7 @@
*/
/**
* Mix-in classes to add Jackson serialization support.
*
* @author Jitendra Singh
* @since 4.2
* Jackson 2 serialization support.
*/
@NullMarked
package org.springframework.security.jackson2;

View File

@ -34,9 +34,9 @@ import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aop.Pointcut;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@ -340,13 +340,14 @@ public class AuthorizationAdvisorProxyFactoryTests {
assertThat(factory.proxy(35)).isEqualTo(35);
}
// TODO Find why callbacks property is serialized with Jackson 3, not with Jackson 2
@Disabled("callbacks property is serialized with Jackson 3, not with Jackson 2")
@Test
public void serializeWhenAuthorizationProxyObjectThenOnlyIncludesProxiedProperties()
throws JsonProcessingException {
public void serializeWhenAuthorizationProxyObjectThenOnlyIncludesProxiedProperties() {
SecurityContextHolder.getContext().setAuthentication(this.admin);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = proxy(factory, this.alan);
ObjectMapper mapper = new ObjectMapper();
JsonMapper mapper = new JsonMapper();
String serialized = mapper.writeValueAsString(user);
Map<String, Object> properties = mapper.readValue(serialized, Map.class);
assertThat(properties).hasSize(3).containsKeys("id", "firstName", "lastName");

View File

@ -0,0 +1,51 @@
/*
* 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.jackson;
import org.junit.jupiter.api.BeforeEach;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
/**
* @author Jitenra Singh
* @since 4.2
*/
public abstract class AbstractMixinTests {
protected JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder()
.allowIfSubType(
"org.springframework.security.jackson.UsernamePasswordAuthenticationTokenMixinTests$NonUserPrincipal");
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader, builder)).build();
}
User createDefaultUser() {
return createUser("admin", "1234", "ROLE_USER");
}
User createUser(String username, String password, String authority) {
return new User(username, password, AuthorityUtils.createAuthorityList(authority));
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.ValueInstantiationException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class AnonymousAuthenticationTokenMixinTests extends AbstractMixinTests {
private static final String HASH_KEY = "key";
// @formatter:off
private static final String ANONYMOUS_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.AnonymousAuthenticationToken\", "
+ "\"details\": null,"
+ "\"principal\": " + UserDeserializerTests.USER_JSON + ","
+ "\"authenticated\": true, "
+ "\"keyHash\": " + HASH_KEY.hashCode() + ","
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+ "}";
// @formatter:on
@Test
public void serializeAnonymousAuthenticationTokenTest() throws JSONException {
User user = createDefaultUser();
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(HASH_KEY, user, user.getAuthorities());
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(ANONYMOUS_JSON, actualJson, true);
}
@Test
public void deserializeAnonymousAuthenticationTokenTest() {
AnonymousAuthenticationToken token = this.mapper.readValue(ANONYMOUS_JSON, AnonymousAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getKeyHash()).isEqualTo(HASH_KEY.hashCode());
assertThat(token.getAuthorities()).isNotNull().hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
@Test
public void deserializeAnonymousAuthenticationTokenWithoutAuthoritiesTest() {
String jsonString = "{\"@class\": \"org.springframework.security.authentication.AnonymousAuthenticationToken\", \"details\": null,"
+ "\"principal\": \"user\", \"authenticated\": true, \"keyHash\": " + HASH_KEY.hashCode() + ","
+ "\"authorities\": [\"java.util.ArrayList\", []]}";
assertThatExceptionOfType(ValueInstantiationException.class)
.isThrownBy(() -> this.mapper.readValue(jsonString, AnonymousAuthenticationToken.class));
}
@Test
public void serializeAnonymousAuthenticationTokenMixinAfterEraseCredentialTest() throws JSONException {
User user = createDefaultUser();
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(HASH_KEY, user, user.getAuthorities());
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(ANONYMOUS_JSON.replace(UserDeserializerTests.USER_PASSWORD, "null"), actualJson, true);
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.authentication.BadCredentialsException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Yannick Lombardi
* @since 5.0
*/
public class BadCredentialsExceptionMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String EXCEPTION_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.BadCredentialsException\","
+ "\"localizedMessage\": \"message\", "
+ "\"message\": \"message\", "
+ "\"suppressed\": [\"[Ljava.lang.Throwable;\",[]]"
+ "}";
// @formatter:on
@Test
public void serializeBadCredentialsExceptionMixinTest() throws JsonProcessingException, JSONException {
BadCredentialsException exception = new BadCredentialsException("message");
String serializedJson = this.mapper.writeValueAsString(exception);
JSONAssert.assertEquals(EXCEPTION_JSON, serializedJson, true);
}
@Test
public void deserializeBadCredentialsExceptionMixinTest() throws IOException {
BadCredentialsException exception = this.mapper.readValue(EXCEPTION_JSON, BadCredentialsException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isEqualTo("message");
assertThat(exception.getLocalizedMessage()).isEqualTo("message");
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.jackson;
import java.time.Instant;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 7.0
*/
class FactorGrantedAuthorityMixinTests extends AbstractMixinTests {
// @formatter:off
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.FactorGrantedAuthority\", \"authority\": \"FACTOR_PASSWORD\", \"issuedAt\": 1759177143.043000000 }";
private Instant issuedAt = Instant.ofEpochMilli(1759177143043L);
// @formatter:on
@Test
void serializeSimpleGrantedAuthorityTest() throws JSONException {
GrantedAuthority authority = FactorGrantedAuthority.withAuthority("FACTOR_PASSWORD")
.issuedAt(this.issuedAt)
.build();
String serializeJson = this.mapper.writeValueAsString(authority);
JSONAssert.assertEquals(AUTHORITY_JSON, serializeJson, true);
}
@Test
void deserializeGrantedAuthorityTest() {
FactorGrantedAuthority authority = (FactorGrantedAuthority) this.mapper.readValue(AUTHORITY_JSON, Object.class);
assertThat(authority).isNotNull();
assertThat(authority.getAuthority()).isEqualTo("FACTOR_PASSWORD");
assertThat(authority.getIssuedAt()).isEqualTo(this.issuedAt);
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.jackson;
import java.io.IOException;
import java.util.Collections;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class RememberMeAuthenticationTokenMixinTests extends AbstractMixinTests {
private static final String REMEMBERME_KEY = "rememberMe";
// @formatter:off
private static final String REMEMBERME_AUTH_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.RememberMeAuthenticationToken\", "
+ "\"keyHash\": " + REMEMBERME_KEY.hashCode() + ", "
+ "\"authenticated\": true, \"details\": null" + ", "
+ "\"principal\": " + UserDeserializerTests.USER_JSON + ", "
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+ "}";
// @formatter:on
// @formatter:off
private static final String REMEMBERME_AUTH_STRINGPRINCIPAL_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.RememberMeAuthenticationToken\","
+ "\"keyHash\": " + REMEMBERME_KEY.hashCode() + ", "
+ "\"authenticated\": true, "
+ "\"details\": null,"
+ "\"principal\": \"admin\", "
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+ "}";
// @formatter:on
@Test
public void testWithNullPrincipal() {
assertThatIllegalArgumentException().isThrownBy(
() -> new RememberMeAuthenticationToken("key", null, Collections.<GrantedAuthority>emptyList()));
}
@Test
public void testWithNullKey() {
assertThatIllegalArgumentException().isThrownBy(
() -> new RememberMeAuthenticationToken(null, "principal", Collections.<GrantedAuthority>emptyList()));
}
@Test
public void serializeRememberMeAuthenticationToken() throws JsonProcessingException, JSONException {
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken(REMEMBERME_KEY, "admin",
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(REMEMBERME_AUTH_STRINGPRINCIPAL_JSON, actualJson, true);
}
@Test
public void serializeRememberMeAuthenticationWithUserToken() throws JsonProcessingException, JSONException {
User user = createDefaultUser();
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken(REMEMBERME_KEY, user,
user.getAuthorities());
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(String.format(REMEMBERME_AUTH_JSON, "\"password\""), actualJson, true);
}
@Test
public void serializeRememberMeAuthenticationWithUserTokenAfterEraseCredential()
throws JsonProcessingException, JSONException {
User user = createDefaultUser();
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken(REMEMBERME_KEY, user,
user.getAuthorities());
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(REMEMBERME_AUTH_JSON.replace(UserDeserializerTests.USER_PASSWORD, "null"), actualJson,
true);
}
@Test
public void deserializeRememberMeAuthenticationToken() throws IOException {
RememberMeAuthenticationToken token = this.mapper.readValue(REMEMBERME_AUTH_STRINGPRINCIPAL_JSON,
RememberMeAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isEqualTo("admin").isEqualTo(token.getName());
assertThat(token.getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
@Test
public void deserializeRememberMeAuthenticationTokenWithUserTest() throws IOException {
RememberMeAuthenticationToken token = this.mapper.readValue(String.format(REMEMBERME_AUTH_JSON, "\"password\""),
RememberMeAuthenticationToken.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(((User) token.getPrincipal()).getAuthorities()).hasSize(1)
.contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(token.getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(((User) token.getPrincipal()).isEnabled()).isEqualTo(true);
}
}

View File

@ -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.jackson;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class SecurityContextMixinTests extends AbstractMixinTests {
// @formatter:off
public static final String SECURITY_CONTEXT_JSON = "{"
+ "\"@class\": \"org.springframework.security.core.context.SecurityContextImpl\", "
+ "\"authentication\": " + UsernamePasswordAuthenticationTokenMixinTests.AUTHENTICATED_STRINGPRINCIPAL_JSON
+ "}";
// @formatter:on
@Test
public void securityContextSerializeTest() throws JsonProcessingException, JSONException {
SecurityContext context = new SecurityContextImpl();
context.setAuthentication(UsernamePasswordAuthenticationToken.authenticated("admin", "1234",
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))));
String actualJson = this.mapper.writeValueAsString(context);
JSONAssert.assertEquals(SECURITY_CONTEXT_JSON, actualJson, true);
}
@Test
public void securityContextDeserializeTest() throws IOException {
SecurityContext context = this.mapper.readValue(SECURITY_CONTEXT_JSON, SecurityContextImpl.class);
assertThat(context).isNotNull();
assertThat(context.getAuthentication()).isNotNull().isInstanceOf(UsernamePasswordAuthenticationToken.class);
assertThat(context.getAuthentication().getPrincipal()).isEqualTo("admin");
assertThat(context.getAuthentication().getCredentials()).isEqualTo("1234");
assertThat(context.getAuthentication().isAuthenticated()).isTrue();
Collection authorities = context.getAuthentication().getAuthorities();
assertThat(authorities).hasSize(1);
assertThat(authorities).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.jackson;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 5.0
*/
public class SecurityJacksonModulesTests {
@Test
public void addModulesWithNoTypeValidatorBuilder() {
ClassLoader loader = getClass().getClassLoader();
List<JacksonModule> modules = SecurityJacksonModules.getModules(loader);
JsonMapper mapper = JsonMapper.builder().addModules(modules).build();
User user = new User("user", null, List.of(new SimpleGrantedAuthority("SCOPE_message:read")));
String json = mapper.writeValueAsString(user);
User deserializedUer = mapper.readerFor(User.class).readValue(json);
assertThat(deserializedUer).isEqualTo(user);
}
@Test
public void addModulesWithDefaultTypeValidatorBuilder() {
ClassLoader loader = getClass().getClassLoader();
List<JacksonModule> modules = SecurityJacksonModules.getModules(loader,
BasicPolymorphicTypeValidator.builder());
JsonMapper mapper = JsonMapper.builder().addModules(modules).build();
User user = new User("user", null, List.of(new SimpleGrantedAuthority("SCOPE_message:read")));
String json = mapper.writeValueAsString(user);
User deserializedUer = mapper.readerFor(User.class).readValue(json);
assertThat(deserializedUer).isEqualTo(user);
}
@Test
public void addModulesWithCustomTypeValidator() {
ClassLoader loader = getClass().getClassLoader();
BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder()
.allowIfSubType(TestGrantedAuthority.class);
List<JacksonModule> modules = SecurityJacksonModules.getModules(loader, builder);
JsonMapper mapper = JsonMapper.builder().addModules(modules).build();
User user = new User("user", null, List.of(new TestGrantedAuthority()));
String json = mapper.writeValueAsString(user);
User deserializedUer = mapper.readerFor(User.class).readValue(json);
assertThat(deserializedUer).isEqualTo(user);
}
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private static class TestGrantedAuthority implements GrantedAuthority {
@Override
public String getAuthority() {
return "test";
}
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.ValueInstantiationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class SimpleGrantedAuthorityMixinTests extends AbstractMixinTests {
// @formatter:off
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", [" + AUTHORITY_JSON + "]]";
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON + "]]";
public static final String NO_AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", []]";
public static final String EMPTY_AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$EmptyList\", []]";
public static final String NO_AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", []]";
// @formatter:on
@Test
public void serializeSimpleGrantedAuthorityTest() throws JsonProcessingException, JSONException {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
String serializeJson = this.mapper.writeValueAsString(authority);
JSONAssert.assertEquals(AUTHORITY_JSON, serializeJson, true);
}
@Test
public void deserializeGrantedAuthorityTest() throws IOException {
SimpleGrantedAuthority authority = this.mapper.readValue(AUTHORITY_JSON, SimpleGrantedAuthority.class);
assertThat(authority).isNotNull();
assertThat(authority.getAuthority()).isNotNull().isEqualTo("ROLE_USER");
}
@Test
public void deserializeGrantedAuthorityWithoutRoleTest() throws IOException {
String json = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\"}";
assertThatExceptionOfType(ValueInstantiationException.class)
.isThrownBy(() -> this.mapper.readValue(json, SimpleGrantedAuthority.class));
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.jackson;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import static org.assertj.core.api.Assertions.assertThat;
class UnmodifiableMapTests extends AbstractMixinTests {
// @formatter:off
private static final String DEFAULT_MAP_JSON = "{"
+ "\"@class\": \"java.util.Collections$UnmodifiableMap\","
+ "\"Key\": \"Value\""
+ "}";
// @formatter:on
@Test
void shouldSerialize() throws Exception {
String mapJson = mapper
.writeValueAsString(Collections.unmodifiableMap(Collections.singletonMap("Key", "Value")));
JSONAssert.assertEquals(DEFAULT_MAP_JSON, mapJson, true);
}
@Test
void shouldDeserialize() throws Exception {
Map<String, String> map = mapper.readValue(DEFAULT_MAP_JSON,
Collections.unmodifiableMap(Collections.emptyMap()).getClass());
assertThat(map).isNotNull()
.isInstanceOf(Collections.unmodifiableMap(Collections.emptyMap()).getClass())
.containsAllEntriesOf(Collections.singletonMap("Key", "Value"));
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.jackson;
import java.io.IOException;
import java.util.Collections;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ObjectNode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class UserDeserializerTests extends AbstractMixinTests {
public static final String USER_PASSWORD = "\"1234\"";
// @formatter:off
public static final String USER_JSON = "{"
+ "\"@class\": \"org.springframework.security.core.userdetails.User\", "
+ "\"username\": \"admin\","
+ " \"password\": " + USER_PASSWORD + ", "
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_SET_JSON
+ "}";
// @formatter:on
@Test
public void serializeUserTest() throws JsonProcessingException, JSONException {
User user = createDefaultUser();
String userJson = this.mapper.writeValueAsString(user);
JSONAssert.assertEquals(userWithPasswordJson(user.getPassword()), userJson, true);
}
@Test
public void serializeUserWithoutAuthority() throws JsonProcessingException, JSONException {
User user = new User("admin", "1234", Collections.<GrantedAuthority>emptyList());
String userJson = this.mapper.writeValueAsString(user);
JSONAssert.assertEquals(userWithNoAuthoritiesJson(), userJson, true);
}
@Test
public void deserializeUserWithNullPasswordEmptyAuthorityTest() throws IOException {
String userJsonWithoutPasswordString = USER_JSON.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_SET_JSON,
"[]");
assertThatExceptionOfType(MismatchedInputException.class)
.isThrownBy(() -> this.mapper.readValue(userJsonWithoutPasswordString, User.class));
}
@Test
public void deserializeUserWithNullPasswordNoAuthorityTest() throws Exception {
String userJsonWithoutPasswordString = removeNode(userWithNoAuthoritiesJson(), this.mapper, "password");
User user = this.mapper.readValue(userJsonWithoutPasswordString, User.class);
assertThat(user).isNotNull();
assertThat(user.getUsername()).isEqualTo("admin");
assertThat(user.getPassword()).isNull();
assertThat(user.getAuthorities()).isEmpty();
assertThat(user.isEnabled()).isEqualTo(true);
}
@Test
public void deserializeUserWithNoClassIdInAuthoritiesTest() throws Exception {
String userJson = USER_JSON.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_SET_JSON,
"[{\"authority\": \"ROLE_USER\"}]");
assertThatExceptionOfType(MismatchedInputException.class)
.isThrownBy(() -> this.mapper.readValue(userJson, User.class));
}
@Test
public void deserializeUserWithClassIdInAuthoritiesTest() {
User user = this.mapper.readValue(userJson(), User.class);
assertThat(user).isNotNull();
assertThat(user.getUsername()).isEqualTo("admin");
assertThat(user.getPassword()).isEqualTo("1234");
assertThat(user.getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
private String removeNode(String json, JsonMapper mapper, String toRemove) throws Exception {
ObjectNode node = mapper.createParser(json).readValueAsTree();
node.remove(toRemove);
String result = mapper.writeValueAsString(node);
JSONAssert.assertNotEquals(json, result, false);
return result;
}
public static String userJson() {
return USER_JSON;
}
public static String userWithPasswordJson(String password) {
return userJson().replaceAll(Pattern.quote(USER_PASSWORD), "\"" + password + "\"");
}
public static String userWithNoAuthoritiesJson() {
return userJson().replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_SET_JSON,
SimpleGrantedAuthorityMixinTests.NO_AUTHORITIES_SET_JSON);
}
}

View File

@ -0,0 +1,221 @@
/*
* 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.jackson;
import java.io.IOException;
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonInclude.Value;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @author Greg Turnquist
* @author Onur Kagan Ozcan
* @since 4.2
*/
public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixinTests {
private static final String AUTHENTICATED_JSON = "{"
+ "\"@class\": \"org.springframework.security.authentication.UsernamePasswordAuthenticationToken\","
+ "\"principal\": " + UserDeserializerTests.USER_JSON + ", " + "\"credentials\": \"1234\", "
+ "\"authenticated\": true, " + "\"details\": null, " + "\"authorities\": "
+ SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON + "}";
public static final String AUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_JSON
.replace(UserDeserializerTests.USER_JSON, "\"admin\"");
private static final String NON_USER_PRINCIPAL_JSON = "{"
+ "\"@class\": \"org.springframework.security.jackson.UsernamePasswordAuthenticationTokenMixinTests$NonUserPrincipal\", "
+ "\"username\": \"admin\"" + "}";
private static final String AUTHENTICATED_STRINGDETAILS_JSON = AUTHENTICATED_JSON.replace("\"details\": null, ",
"\"details\": \"details\", ");
private static final String AUTHENTICATED_NON_USER_PRINCIPAL_JSON = AUTHENTICATED_JSON
.replace(UserDeserializerTests.USER_JSON, NON_USER_PRINCIPAL_JSON)
.replaceAll(UserDeserializerTests.USER_PASSWORD, "null")
.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON,
SimpleGrantedAuthorityMixinTests.NO_AUTHORITIES_ARRAYLIST_JSON);
private static final String UNAUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_STRINGPRINCIPAL_JSON
.replace("\"authenticated\": true, ", "\"authenticated\": false, ")
.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON,
SimpleGrantedAuthorityMixinTests.EMPTY_AUTHORITIES_ARRAYLIST_JSON);
@Test
public void serializeUnauthenticatedUsernamePasswordAuthenticationTokenMixinTest()
throws JsonProcessingException, JSONException {
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated("admin",
"1234");
String serializedJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(UNAUTHENTICATED_STRINGPRINCIPAL_JSON, serializedJson, true);
}
@Test
public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinTest()
throws JsonProcessingException, JSONException {
User user = createDefaultUser();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.authenticated(user.getUsername(), user.getPassword(), user.getAuthorities());
String serializedJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(AUTHENTICATED_STRINGPRINCIPAL_JSON, serializedJson, true);
}
@Test
public void deserializeUnauthenticatedUsernamePasswordAuthenticationTokenMixinTest() {
UsernamePasswordAuthenticationToken token = this.mapper.readValue(UNAUTHENTICATED_STRINGPRINCIPAL_JSON,
UsernamePasswordAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.isAuthenticated()).isEqualTo(false);
assertThat(token.getAuthorities()).isNotNull().hasSize(0);
}
@Test
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenMixinTest() {
UsernamePasswordAuthenticationToken expectedToken = createToken();
UsernamePasswordAuthenticationToken token = this.mapper.readValue(AUTHENTICATED_STRINGPRINCIPAL_JSON,
UsernamePasswordAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.isAuthenticated()).isTrue();
assertThat(token.getAuthorities()).isEqualTo(expectedToken.getAuthorities());
}
@Test
public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinWithUserTest()
throws JsonProcessingException, JSONException {
UsernamePasswordAuthenticationToken token = createToken();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(AUTHENTICATED_JSON, actualJson, true);
}
@Test
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithUserTest() throws IOException {
UsernamePasswordAuthenticationToken token = this.mapper.readValue(AUTHENTICATED_JSON,
UsernamePasswordAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
assertThat(((User) token.getPrincipal()).getAuthorities()).isNotNull()
.hasSize(1)
.contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(token.isAuthenticated()).isEqualTo(true);
assertThat(token.getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
}
@Test
public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinAfterEraseCredentialInvoked()
throws JsonProcessingException, JSONException {
UsernamePasswordAuthenticationToken token = createToken();
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(AUTHENTICATED_JSON.replaceAll(UserDeserializerTests.USER_PASSWORD, "null"), actualJson,
true);
}
@Test
public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinWithNonUserPrincipalTest()
throws JsonProcessingException, JSONException {
NonUserPrincipal principal = new NonUserPrincipal();
principal.setUsername("admin");
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(principal, null,
new ArrayList<>());
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(AUTHENTICATED_NON_USER_PRINCIPAL_JSON, actualJson, true);
}
@Test
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithNonUserPrincipalTest()
throws IOException {
UsernamePasswordAuthenticationToken token = this.mapper.readValue(AUTHENTICATED_NON_USER_PRINCIPAL_JSON,
UsernamePasswordAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(NonUserPrincipal.class);
}
@Test
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithDetailsTest() {
UsernamePasswordAuthenticationToken token = this.mapper.readValue(AUTHENTICATED_STRINGDETAILS_JSON,
UsernamePasswordAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
assertThat(((User) token.getPrincipal()).getAuthorities()).isNotNull()
.hasSize(1)
.contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(token.isAuthenticated()).isEqualTo(true);
assertThat(token.getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
assertThat(token.getDetails()).isExactlyInstanceOf(String.class).isEqualTo("details");
}
@Test
public void serializingThenDeserializingWithNoCredentialsOrDetailsShouldWork() {
UsernamePasswordAuthenticationToken original = UsernamePasswordAuthenticationToken.unauthenticated("Frodo",
null);
String serialized = this.mapper.writeValueAsString(original);
UsernamePasswordAuthenticationToken deserialized = this.mapper.readValue(serialized,
UsernamePasswordAuthenticationToken.class);
assertThat(deserialized).isEqualTo(original);
}
@Test
public void serializingThenDeserializingWithConfiguredJsontMapperShouldWork() {
JsonMapper jsonMapper = this.mapper.rebuild()
.changeDefaultPropertyInclusion((p) -> Value.construct(Include.NON_ABSENT, Include.NON_ABSENT))
.build();
UsernamePasswordAuthenticationToken original = UsernamePasswordAuthenticationToken.unauthenticated("Frodo",
null);
String serialized = jsonMapper.writeValueAsString(original);
UsernamePasswordAuthenticationToken deserialized = jsonMapper.readValue(serialized,
UsernamePasswordAuthenticationToken.class);
assertThat(deserialized).isEqualTo(original);
}
private UsernamePasswordAuthenticationToken createToken() {
User user = createDefaultUser();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(user,
user.getPassword(), user.getAuthorities());
return token;
}
@JsonClassDescription
public static class NonUserPrincipal {
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}
}

View File

@ -4,7 +4,7 @@
Spring Security provides Jackson support for persisting Spring Security related classes.
This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
To use it, register the `SecurityJacksonModules.getModules(ClassLoader)` with `JsonMapper.Builder` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
[tabs]
======
@ -12,12 +12,12 @@ Java::
+
[source,java,role="primary"]
----
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);
JsonMapper mapper = JsonMapper.builder()
.addModules(SecurityJacksonModules.getModules(loader))
.build();
// ... use ObjectMapper as normally ...
// ... use JsonMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);
@ -27,12 +27,12 @@ Kotlin::
+
[source,kotlin,role="secondary"]
----
val mapper = ObjectMapper()
val loader = javaClass.classLoader
val modules: MutableList<Module> = SecurityJackson2Modules.getModules(loader)
mapper.registerModules(modules)
val mapper = JsonMapper.builder()
.addModules(SecurityJacksonModules.getModules(loader))
.build()
// ... use ObjectMapper as normally ...
// ... use JsonMapper as normally ...
val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
@ -43,8 +43,8 @@ val json: String = mapper.writeValueAsString(context)
====
The following Spring Security modules provide Jackson support:
- spring-security-core (`CoreJackson2Module`)
- spring-security-web (`WebJackson2Module`, `WebServletJackson2Module`, `WebServerJackson2Module`)
- xref:servlet/oauth2/client/index.adoc#oauth2client[ spring-security-oauth2-client] (`OAuth2ClientJackson2Module`)
- spring-security-cas (`CasJackson2Module`)
- spring-security-core (`CoreJacksonModule`)
- spring-security-web (`WebJacksonModule`, `WebServletJacksonModule`, `WebServerJacksonModule`)
- xref:servlet/oauth2/client/index.adoc#oauth2client[ spring-security-oauth2-client] (`OAuth2ClientJacksonModule`)
- spring-security-cas (`CasJacksonModule`)
====

View File

@ -4,16 +4,16 @@
Spring Security provides Jackson support for persisting Spring Security-related classes.
This can improve the performance of serializing Spring Security-related classes when working with distributed sessions (session replication, Spring Session, and so on).
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
To use it, register the `SecurityJacksonModules.getModules(ClassLoader)` with `JsonMapper.Builder` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
[source,java]
----
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);
JsonMapper mapper = JsonMapper.builder()
.addModules(SecurityJacksonModules.getModules(loader))
.build();
// ... use ObjectMapper as normally ...
// ... use JsonMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);
@ -23,8 +23,8 @@ String json = mapper.writeValueAsString(context);
====
The following Spring Security modules provide Jackson support:
- spring-security-core (javadoc:org.springframework.security.jackson2.CoreJackson2Module[])
- spring-security-web (javadoc:org.springframework.security.web.jackson2.WebJackson2Module[], javadoc:org.springframework.security.web.jackson2.WebServletJackson2Module[], javadoc:org.springframework.security.web.server.jackson2.WebServerJackson2Module[])
- <<oauth2client, spring-security-oauth2-client>> (javadoc:org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module[])
- spring-security-cas (javadoc:org.springframework.security.cas.jackson2.CasJackson2Module[])
- spring-security-core (javadoc:org.springframework.security.jackson.CoreJacksonModule[])
- spring-security-web (javadoc:org.springframework.security.web.jackson.WebJacksonModule[], javadoc:org.springframework.security.web.jackson.WebServletJacksonModule[], javadoc:org.springframework.security.web.server.jackson.WebServerJacksonModule[])
- <<oauth2client, spring-security-oauth2-client>> (javadoc:org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule[])
- spring-security-cas (javadoc:org.springframework.security.cas.jackson.CasJacksonModule[])
====

View File

@ -11,6 +11,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'ldapsdk:ldapsdk'
optional "com.unboundid:unboundid-ldapsdk"
optional 'tools.jackson.core:jackson-databind'
api ('org.springframework.ldap:spring-ldap-core') {
exclude(group: 'commons-logging', module: 'commons-logging')
exclude(group: 'org.springframework', module: 'spring-beans')

View File

@ -0,0 +1,38 @@
/*
* 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.ldap.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.InetOrgPerson;
/**
* This Jackson mixin is used to serialize/deserialize {@link InetOrgPerson}.
*
* @author Sebastien Deleuze
* @since 7.0
* @see LdapJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class InetOrgPersonMixin {
}

View File

@ -0,0 +1,47 @@
/*
* 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.ldap.jackson;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.LdapAuthority;
/**
* This Jackson mixin is used to serialize/deserialize {@link LdapAuthority}.
*
* @author Sebastien Deleuze
* @since 7.0
* @see LdapJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class LdapAuthorityMixin {
@JsonCreator
LdapAuthorityMixin(@JsonProperty("role") String role, @JsonProperty("dn") String dn,
@JsonProperty("attributes") Map<String, List<String>> attributes) {
}
}

View File

@ -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.ldap.jackson;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.jackson.SecurityJacksonModule;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.InetOrgPerson;
import org.springframework.security.ldap.userdetails.LdapAuthority;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
import org.springframework.security.ldap.userdetails.Person;
/**
* Jackson module for {@code spring-security-ldap}. This module registers
* {@link LdapAuthorityMixin}, {@link LdapUserDetailsImplMixin}, {@link PersonMixin},
* {@link InetOrgPersonMixin}.
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @since 7.0
* @see SecurityJacksonModules
*/
@SuppressWarnings("serial")
public class LdapJacksonModule extends SecurityJacksonModule {
public LdapJacksonModule() {
super(LdapJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(InetOrgPerson.class)
.allowIfSubType(LdapUserDetailsImpl.class)
.allowIfSubType(Person.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(LdapAuthority.class, LdapAuthorityMixin.class);
context.setMixIn(LdapUserDetailsImpl.class, LdapUserDetailsImplMixin.class);
context.setMixIn(Person.class, PersonMixin.class);
context.setMixIn(InetOrgPerson.class, InetOrgPersonMixin.class);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.ldap.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
/**
* This Jackson mixin is used to serialize/deserialize {@link LdapUserDetailsImpl}.
*
* @author Sebastien Deleuze
* @since 7.0
* @see LdapJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class LdapUserDetailsImplMixin {
}

View File

@ -0,0 +1,38 @@
/*
* 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.ldap.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.Person;
/**
* This Jackson mixin is used to serialize/deserialize {@link Person}.
*
* @author Sebastien Deleuze
* @since 7.0
* @see LdapJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class PersonMixin {
}

View File

@ -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 LDAP.
*/
package org.springframework.security.ldap.jackson;

View File

@ -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 2 serialization support for LDAP.
*/
package org.springframework.security.ldap.jackson2;

View File

@ -0,0 +1,194 @@
/*
* 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.ldap.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.InetOrgPerson;
import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link org.springframework.security.ldap.jackson.InetOrgPersonMixin}.
*/
public class InetOrgPersonMixinTests {
private static final String USER_PASSWORD = "Password1234";
private static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", []]";
// @formatter:off
private static final String INET_ORG_PERSON_JSON = "{\n"
+ "\"@class\": \"org.springframework.security.ldap.userdetails.InetOrgPerson\","
+ "\"dn\": \"ignored=ignored\","
+ "\"uid\": \"ghengis\","
+ "\"username\": \"ghengis\","
+ "\"password\": \"" + USER_PASSWORD + "\","
+ "\"carLicense\": \"HORS1\","
+ "\"givenName\": \"Ghengis\","
+ "\"destinationIndicator\": \"West\","
+ "\"displayName\": \"Ghengis McCann\","
+ "\"givenName\": \"Ghengis\","
+ "\"homePhone\": \"+467575436521\","
+ "\"initials\": \"G\","
+ "\"employeeNumber\": \"00001\","
+ "\"homePostalAddress\": \"Steppes\","
+ "\"mail\": \"ghengis@mongolia\","
+ "\"mobile\": \"always\","
+ "\"o\": \"Hordes\","
+ "\"ou\": \"Horde1\","
+ "\"postalAddress\": \"On the Move\","
+ "\"postalCode\": \"Changes Frequently\","
+ "\"roomNumber\": \"Yurt 1\","
+ "\"sn\": \"Khan\","
+ "\"street\": \"Westward Avenue\","
+ "\"telephoneNumber\": \"+442075436521\","
+ "\"departmentNumber\": \"5679\","
+ "\"title\": \"T\","
+ "\"cn\": [\"java.util.Arrays$ArrayList\",[\"Ghengis Khan\"]],"
+ "\"description\": \"Scary\","
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + ","
+ "\"graceLoginsRemaining\": " + Integer.MAX_VALUE + ","
+ "\"timeBeforeExpiration\": " + Integer.MAX_VALUE
+ "}";
// @formatter:on
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
InetOrgPersonContextMapper mapper = new InetOrgPersonContextMapper();
InetOrgPerson p = (InetOrgPerson) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
String json = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(INET_ORG_PERSON_JSON, json, true);
}
@Test
public void serializeWhenEraseCredentialInvokedThenUserPasswordIsNull() throws JacksonException, JSONException {
InetOrgPersonContextMapper mapper = new InetOrgPersonContextMapper();
InetOrgPerson p = (InetOrgPerson) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
p.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(INET_ORG_PERSON_JSON.replaceAll("\"" + USER_PASSWORD + "\"", "null"), actualJson, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(INET_ORG_PERSON_JSON, InetOrgPerson.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
InetOrgPersonContextMapper mapper = new InetOrgPersonContextMapper();
InetOrgPerson expectedAuthentication = (InetOrgPerson) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
InetOrgPerson authentication = this.mapper.readValue(INET_ORG_PERSON_JSON, InetOrgPerson.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getCarLicense()).isEqualTo(expectedAuthentication.getCarLicense());
assertThat(authentication.getDepartmentNumber()).isEqualTo(expectedAuthentication.getDepartmentNumber());
assertThat(authentication.getDestinationIndicator())
.isEqualTo(expectedAuthentication.getDestinationIndicator());
assertThat(authentication.getDn()).isEqualTo(expectedAuthentication.getDn());
assertThat(authentication.getDescription()).isEqualTo(expectedAuthentication.getDescription());
assertThat(authentication.getDisplayName()).isEqualTo(expectedAuthentication.getDisplayName());
assertThat(authentication.getUid()).isEqualTo(expectedAuthentication.getUid());
assertThat(authentication.getUsername()).isEqualTo(expectedAuthentication.getUsername());
assertThat(authentication.getPassword()).isEqualTo(expectedAuthentication.getPassword());
assertThat(authentication.getHomePhone()).isEqualTo(expectedAuthentication.getHomePhone());
assertThat(authentication.getEmployeeNumber()).isEqualTo(expectedAuthentication.getEmployeeNumber());
assertThat(authentication.getHomePostalAddress()).isEqualTo(expectedAuthentication.getHomePostalAddress());
assertThat(authentication.getInitials()).isEqualTo(expectedAuthentication.getInitials());
assertThat(authentication.getMail()).isEqualTo(expectedAuthentication.getMail());
assertThat(authentication.getMobile()).isEqualTo(expectedAuthentication.getMobile());
assertThat(authentication.getO()).isEqualTo(expectedAuthentication.getO());
assertThat(authentication.getOu()).isEqualTo(expectedAuthentication.getOu());
assertThat(authentication.getPostalAddress()).isEqualTo(expectedAuthentication.getPostalAddress());
assertThat(authentication.getPostalCode()).isEqualTo(expectedAuthentication.getPostalCode());
assertThat(authentication.getRoomNumber()).isEqualTo(expectedAuthentication.getRoomNumber());
assertThat(authentication.getStreet()).isEqualTo(expectedAuthentication.getStreet());
assertThat(authentication.getSn()).isEqualTo(expectedAuthentication.getSn());
assertThat(authentication.getTitle()).isEqualTo(expectedAuthentication.getTitle());
assertThat(authentication.getGivenName()).isEqualTo(expectedAuthentication.getGivenName());
assertThat(authentication.getTelephoneNumber()).isEqualTo(expectedAuthentication.getTelephoneNumber());
assertThat(authentication.getGraceLoginsRemaining())
.isEqualTo(expectedAuthentication.getGraceLoginsRemaining());
assertThat(authentication.getTimeBeforeExpiration())
.isEqualTo(expectedAuthentication.getTimeBeforeExpiration());
assertThat(authentication.isAccountNonExpired()).isEqualTo(expectedAuthentication.isAccountNonExpired());
assertThat(authentication.isAccountNonLocked()).isEqualTo(expectedAuthentication.isAccountNonLocked());
assertThat(authentication.isEnabled()).isEqualTo(expectedAuthentication.isEnabled());
assertThat(authentication.isCredentialsNonExpired())
.isEqualTo(expectedAuthentication.isCredentialsNonExpired());
}
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setDn(LdapNameBuilder.newInstance("ignored=ignored").build());
ctx.setAttributeValue("uid", "ghengis");
ctx.setAttributeValue("userPassword", USER_PASSWORD);
ctx.setAttributeValue("carLicense", "HORS1");
ctx.setAttributeValue("cn", "Ghengis Khan");
ctx.setAttributeValue("description", "Scary");
ctx.setAttributeValue("destinationIndicator", "West");
ctx.setAttributeValue("displayName", "Ghengis McCann");
ctx.setAttributeValue("givenName", "Ghengis");
ctx.setAttributeValue("homePhone", "+467575436521");
ctx.setAttributeValue("initials", "G");
ctx.setAttributeValue("employeeNumber", "00001");
ctx.setAttributeValue("homePostalAddress", "Steppes");
ctx.setAttributeValue("mail", "ghengis@mongolia");
ctx.setAttributeValue("mobile", "always");
ctx.setAttributeValue("o", "Hordes");
ctx.setAttributeValue("ou", "Horde1");
ctx.setAttributeValue("postalAddress", "On the Move");
ctx.setAttributeValue("postalCode", "Changes Frequently");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("sn", "Khan");
ctx.setAttributeValue("street", "Westward Avenue");
ctx.setAttributeValue("telephoneNumber", "+442075436521");
ctx.setAttributeValue("departmentNumber", "5679");
ctx.setAttributeValue("title", "T");
return ctx;
}
}

View File

@ -0,0 +1,124 @@
/*
* 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.ldap.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link org.springframework.security.ldap.jackson.LdapUserDetailsImplMixin}.
*/
public class LdapUserDetailsImplMixinTests {
private static final String USER_PASSWORD = "Password1234";
private static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", []]";
// @formatter:off
private static final String USER_JSON = "{"
+ "\"@class\": \"org.springframework.security.ldap.userdetails.LdapUserDetailsImpl\", "
+ "\"dn\": \"ignored=ignored\","
+ "\"username\": \"ghengis\","
+ "\"password\": \"" + USER_PASSWORD + "\","
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + ","
+ "\"graceLoginsRemaining\": " + Integer.MAX_VALUE + ","
+ "\"timeBeforeExpiration\": " + Integer.MAX_VALUE
+ "}";
// @formatter:on
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
LdapUserDetailsImpl p = (LdapUserDetailsImpl) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
String json = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(USER_JSON, json, true);
}
@Test
public void serializeWhenEraseCredentialInvokedThenUserPasswordIsNull() throws JacksonException, JSONException {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
LdapUserDetailsImpl p = (LdapUserDetailsImpl) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
p.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(USER_JSON.replaceAll("\"" + USER_PASSWORD + "\"", "null"), actualJson, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(USER_JSON, LdapUserDetailsImpl.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
LdapUserDetailsImpl expectedAuthentication = (LdapUserDetailsImpl) mapper
.mapUserFromContext(createUserContext(), "ghengis", AuthorityUtils.NO_AUTHORITIES);
LdapUserDetailsImpl authentication = this.mapper.readValue(USER_JSON, LdapUserDetailsImpl.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDn()).isEqualTo(expectedAuthentication.getDn());
assertThat(authentication.getUsername()).isEqualTo(expectedAuthentication.getUsername());
assertThat(authentication.getPassword()).isEqualTo(expectedAuthentication.getPassword());
assertThat(authentication.getGraceLoginsRemaining())
.isEqualTo(expectedAuthentication.getGraceLoginsRemaining());
assertThat(authentication.getTimeBeforeExpiration())
.isEqualTo(expectedAuthentication.getTimeBeforeExpiration());
assertThat(authentication.isAccountNonExpired()).isEqualTo(expectedAuthentication.isAccountNonExpired());
assertThat(authentication.isAccountNonLocked()).isEqualTo(expectedAuthentication.isAccountNonLocked());
assertThat(authentication.isEnabled()).isEqualTo(expectedAuthentication.isEnabled());
assertThat(authentication.isCredentialsNonExpired())
.isEqualTo(expectedAuthentication.isCredentialsNonExpired());
}
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setDn(LdapNameBuilder.newInstance("ignored=ignored").build());
ctx.setAttributeValue("userPassword", USER_PASSWORD);
return ctx;
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.ldap.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.ldap.userdetails.Person;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link org.springframework.security.ldap.jackson.PersonMixin}.
*/
@SuppressWarnings("removal")
public class PersonMixinTests {
private static final String USER_PASSWORD = "Password1234";
private static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", []]";
// @formatter:off
private static final String PERSON_JSON = "{"
+ "\"@class\": \"org.springframework.security.ldap.userdetails.Person\", "
+ "\"dn\": \"ignored=ignored\","
+ "\"username\": \"ghengis\","
+ "\"password\": \"" + USER_PASSWORD + "\","
+ "\"givenName\": \"Ghengis\","
+ "\"sn\": \"Khan\","
+ "\"cn\": [\"java.util.Arrays$ArrayList\",[\"Ghengis Khan\"]],"
+ "\"description\": \"Scary\","
+ "\"telephoneNumber\": \"+442075436521\","
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + ","
+ "\"graceLoginsRemaining\": " + Integer.MAX_VALUE + ","
+ "\"timeBeforeExpiration\": " + Integer.MAX_VALUE
+ "}";
// @formatter:on
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
PersonContextMapper mapper = new PersonContextMapper();
Person p = (Person) mapper.mapUserFromContext(createUserContext(), "ghengis", AuthorityUtils.NO_AUTHORITIES);
String json = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(PERSON_JSON, json, true);
}
@Test
public void serializeWhenEraseCredentialInvokedThenUserPasswordIsNull() throws JacksonException, JSONException {
PersonContextMapper mapper = new PersonContextMapper();
Person p = (Person) mapper.mapUserFromContext(createUserContext(), "ghengis", AuthorityUtils.NO_AUTHORITIES);
p.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(p);
JSONAssert.assertEquals(PERSON_JSON.replaceAll("\"" + USER_PASSWORD + "\"", "null"), actualJson, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(PERSON_JSON, Person.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
PersonContextMapper mapper = new PersonContextMapper();
Person expectedAuthentication = (Person) mapper.mapUserFromContext(createUserContext(), "ghengis",
AuthorityUtils.NO_AUTHORITIES);
Person authentication = this.mapper.readValue(PERSON_JSON, Person.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDn()).isEqualTo(expectedAuthentication.getDn());
assertThat(authentication.getDescription()).isEqualTo(expectedAuthentication.getDescription());
assertThat(authentication.getUsername()).isEqualTo(expectedAuthentication.getUsername());
assertThat(authentication.getPassword()).isEqualTo(expectedAuthentication.getPassword());
assertThat(authentication.getSn()).isEqualTo(expectedAuthentication.getSn());
assertThat(authentication.getGivenName()).isEqualTo(expectedAuthentication.getGivenName());
assertThat(authentication.getTelephoneNumber()).isEqualTo(expectedAuthentication.getTelephoneNumber());
assertThat(authentication.getGraceLoginsRemaining())
.isEqualTo(expectedAuthentication.getGraceLoginsRemaining());
assertThat(authentication.getTimeBeforeExpiration())
.isEqualTo(expectedAuthentication.getTimeBeforeExpiration());
assertThat(authentication.isAccountNonExpired()).isEqualTo(expectedAuthentication.isAccountNonExpired());
assertThat(authentication.isAccountNonLocked()).isEqualTo(expectedAuthentication.isAccountNonLocked());
assertThat(authentication.isEnabled()).isEqualTo(expectedAuthentication.isEnabled());
assertThat(authentication.isCredentialsNonExpired())
.isEqualTo(expectedAuthentication.isCredentialsNonExpired());
}
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setDn(LdapNameBuilder.newInstance("ignored=ignored").build());
ctx.setAttributeValue("userPassword", USER_PASSWORD);
ctx.setAttributeValue("cn", "Ghengis Khan");
ctx.setAttributeValue("description", "Scary");
ctx.setAttributeValue("givenName", "Ghengis");
ctx.setAttributeValue("sn", "Khan");
ctx.setAttributeValue("telephoneNumber", "+442075436521");
return ctx;
}
}

View File

@ -11,10 +11,11 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging"
}
api "com.nimbusds:nimbus-jose-jwt"
api "com.fasterxml.jackson.core:jackson-databind"
api 'tools.jackson.core:jackson-databind'
optional "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
optional "org.springframework:spring-jdbc"
optional "com.fasterxml.jackson.core:jackson-databind"
testImplementation project(":spring-security-test")
testImplementation project(path : ':spring-security-oauth2-jose', configuration : 'tests')

View File

@ -33,13 +33,15 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
@ -64,8 +66,10 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -469,16 +473,12 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private LobHandler lobHandler = new DefaultLobHandler();
private ObjectMapper objectMapper = new ObjectMapper();
private Mapper mapper = (ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper",
OAuth2AuthorizationRowMapper.class.getClassLoader())) ? new JacksonDelegate() : new Jackson2Delegate();
public OAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@ -623,9 +623,9 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
this.lobHandler = lobHandler;
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
public final void setMapper(Mapper mapper) {
Assert.notNull(mapper, "objectMapper cannot be null");
this.mapper = mapper;
}
protected final RegisteredClientRepository getRegisteredClientRepository() {
@ -636,13 +636,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return this.lobHandler;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
protected final Mapper getMapper() {
return this.mapper;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<>() {
return this.mapper.readValue(data, new ParameterizedTypeReference<>() {
});
}
catch (Exception ex) {
@ -659,13 +659,10 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
public static class OAuth2AuthorizationParametersMapper
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
private ObjectMapper objectMapper = new ObjectMapper();
private Mapper mapper = (ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper",
OAuth2AuthorizationRowMapper.class.getClassLoader())) ? new JacksonDelegate() : new Jackson2Delegate();
public OAuth2AuthorizationParametersMapper() {
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@ -737,13 +734,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return parameters;
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
public final void setMapper(Mapper mapper) {
Assert.notNull(mapper, "mapper cannot be null");
this.mapper = mapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
protected final Mapper getMapper() {
return this.mapper;
}
private <T extends OAuth2Token> List<SqlParameterValue> toSqlParameterList(String tokenColumnName,
@ -774,7 +771,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
return this.mapper.writeValueAsString(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
@ -851,4 +848,74 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
public interface Mapper {
String writeValueAsString(Object data);
<T> T readValue(String value, ParameterizedTypeReference<T> typeReference);
}
@SuppressWarnings("removal")
public static class Jackson2Delegate implements Mapper {
private final ObjectMapper objectMapper = new ObjectMapper();
public Jackson2Delegate() {
ClassLoader classLoader = Jackson2Delegate.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public String writeValueAsString(Object data) {
try {
return this.objectMapper.writeValueAsString(data);
}
catch (JsonProcessingException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
@Override
public <T> T readValue(String value, ParameterizedTypeReference<T> typeReference) {
try {
com.fasterxml.jackson.databind.JavaType javaType = this.objectMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.objectMapper.readValue(value, javaType);
}
catch (JsonProcessingException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
}
public static class JacksonDelegate implements Mapper {
private final JsonMapper jsonMapper;
public JacksonDelegate() {
this.jsonMapper = JsonMapper.builder().addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
public JacksonDelegate(JsonMapper.Builder builder) {
this.jsonMapper = builder.addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
@Override
public String writeValueAsString(Object data) {
return this.jsonMapper.writeValueAsString(data);
}
@Override
public <T> T readValue(String value, ParameterizedTypeReference<T> typeReference) {
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.jsonMapper.readValue(value, javaType);
}
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.Map;
import java.util.Set;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
/**
* Utility class for {@code JsonNode}.
*
* @author Joe Grandja
* @since 7.0
*/
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isString()) ? value.stringValue() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
DeserializationContext context) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainer())
? context.readTreeAsValue(value, context.getTypeFactory().constructType(valueTypeReference)) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* This mixin class is used to serialize/deserialize {@link SignatureAlgorithm}.
*
* @author Joe Grandja
* @since 7.0
* @see SignatureAlgorithm
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class JwsAlgorithmMixin {
}

View File

@ -0,0 +1,77 @@
/*
* 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.oauth2.server.authorization.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.exc.InvalidFormatException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestMixin
*/
final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAuth2AuthorizationRequest> {
@Override
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) {
JsonNode root = context.readTree(parser);
return deserialize(parser, context, root);
}
private OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context, JsonNode root) {
AuthorizationGrantType authorizationGrantType = convertAuthorizationGrantType(
JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, context));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, context));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, context));
return builder.build();
}
private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.authorizationCode();
}
throw new InvalidFormatException(parser, "Invalid authorizationGrantType", authorizationGrantType,
AuthorizationGrantType.class);
}
private static AuthorizationGrantType convertAuthorizationGrantType(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
return null;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
* It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestDeserializer
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizationRequestMixin {
}

View File

@ -0,0 +1,95 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.net.URL;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.core.Version;
import tools.jackson.databind.DefaultTyping;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.jackson.CoreJacksonModule;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* Jackson {@code Module} for {@code spring-security-oauth2-authorization-server}, that
* registers the following mix-in annotations:
*
* <ul>
* <li>{@link OAuth2TokenExchangeActor}</li>
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
* <li>{@link OAuth2TokenExchangeCompositeAuthenticationTokenMixin}</li>
* <li>{@link JwsAlgorithmMixin}</li>
* <li>{@link OAuth2TokenFormatMixin}</li>
* </ul>
*
* If not already enabled, default typing will be automatically enabled as type info is
* required to properly serialize/deserialize objects. In order to use this module just
* add it to your {@code JsonMapper.Builder} configuration.
*
* <pre>
* JsonMapper mapper = JsonMapper.builder()
* .addModules(new OAuth2AuthorizationServerJacksonModule()).build;
* </pre>
*
* @author Sebastien Deleuze
* @author Steve Riesenberg
* @since 7.0
*/
@SuppressWarnings("serial")
public class OAuth2AuthorizationServerJacksonModule extends CoreJacksonModule {
public OAuth2AuthorizationServerJacksonModule() {
super(OAuth2AuthorizationServerJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
super.configurePolymorphicTypeValidator(builder);
builder.allowIfSubType(OAuth2TokenFormat.class)
.allowIfSubType(OAuth2TokenExchangeActor.class)
.allowIfSubType(OAuth2TokenExchangeCompositeAuthenticationToken.class)
.allowIfSubType(SignatureAlgorithm.class)
.allowIfSubType(MacAlgorithm.class)
.allowIfSubType(OAuth2AuthorizationRequest.class)
.allowIfSubType(URL.class);
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder();
this.configurePolymorphicTypeValidator(builder);
((MapperBuilder<?, ?>) context.getOwner()).activateDefaultTyping(builder.build(), DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
context.setMixIn(OAuth2TokenExchangeActor.class, OAuth2TokenExchangeActorMixin.class);
context.setMixIn(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
context.setMixIn(OAuth2TokenExchangeCompositeAuthenticationToken.class,
OAuth2TokenExchangeCompositeAuthenticationTokenMixin.class);
context.setMixIn(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixIn(MacAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixIn(OAuth2TokenFormat.class, OAuth2TokenFormatMixin.class);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeActor}.
*
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2TokenExchangeActor
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenExchangeActorMixin {
@JsonCreator
OAuth2TokenExchangeActorMixin(@JsonProperty("claims") Map<String, Object> claims) {
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
/**
* This mixin class is used to serialize/deserialize
* {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
*
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2TokenExchangeCompositeAuthenticationToken
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenExchangeCompositeAuthenticationTokenMixin {
@JsonCreator
OAuth2TokenExchangeCompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
@JsonProperty("actors") List<Authentication> actors) {
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2TokenFormat}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2TokenFormat
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenFormatMixin {
@JsonCreator
OAuth2TokenFormatMixin(@JsonProperty("value") String value) {
}
}

View File

@ -29,11 +29,11 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
@ -747,7 +747,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
private Map<String, Object> parseMap(String data) {
try {
return getObjectMapper().readValue(data, new TypeReference<>() {
return getMapper().readValue(data, new ParameterizedTypeReference<>() {
});
}
catch (Exception ex) {
@ -852,7 +852,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
private String writeMap(Map<String, Object> data) {
try {
return getObjectMapper().writeValueAsString(data);
return getMapper().writeValueAsString(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);

View File

@ -365,6 +365,7 @@ public class JdbcRegisteredClientRepositoryTests {
return !result.isEmpty() ? result.get(0) : null;
}
@SuppressWarnings("removal")
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private final ObjectMapper objectMapper = new ObjectMapper();

View File

@ -0,0 +1,112 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerJackson2Module}.
*
* @author Steve Riesenberg
* @author Joe Grandja
*/
@SuppressWarnings("removal")
public class OAuth2AuthorizationServerJacksonModuleTests {
private static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
private JsonMapper mapper;
@BeforeEach
public void setup() {
this.mapper = JsonMapper.builder().addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
@Test
public void readValueWhenOAuth2AuthorizationAttributesThenSuccess() {
Authentication principal = new UsernamePasswordAuthenticationToken("principal", "credentials");
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
.attributes((attrs) -> attrs.put(Principal.class.getName(), principal))
.build();
Map<String, Object> attributes = authorization.getAttributes();
String json = this.mapper.writeValueAsString(attributes);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(attributes);
}
@Test
public void readValueWhenOAuth2AccessTokenMetadataThenSuccess() {
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
Map<String, Object> metadata = authorization.getAccessToken().getMetadata();
String json = this.mapper.writeValueAsString(metadata);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(metadata);
}
@Test
public void readValueWhenClientSettingsThenSuccess() {
ClientSettings clientSettings = ClientSettings.builder()
.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
.build();
Map<String, Object> clientSettingsMap = clientSettings.getSettings();
String json = this.mapper.writeValueAsString(clientSettingsMap);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(clientSettingsMap);
}
@Test
public void readValueWhenTokenSettingsThenSuccess() {
TokenSettings tokenSettings = TokenSettings.builder().build();
Map<String, Object> tokenSettingsMap = tokenSettings.getSettings();
String json = this.mapper.writeValueAsString(tokenSettingsMap);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(tokenSettingsMap);
}
@Test
public void readValueWhenOAuth2TokenExchangeCompositeAuthenticationTokenThenSuccess() {
Authentication subject = new UsernamePasswordAuthenticationToken("principal", "credentials");
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(
Map.of(JwtClaimNames.ISS, "issuer-1", JwtClaimNames.SUB, "actor1"));
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(
Map.of(JwtClaimNames.ISS, "issuer-2", JwtClaimNames.SUB, "actor2"));
OAuth2TokenExchangeCompositeAuthenticationToken authentication = new OAuth2TokenExchangeCompositeAuthenticationToken(
subject, List.of(actor1, actor2));
String json = this.mapper.writeValueAsString(authentication);
assertThat(this.mapper.readValue(json, OAuth2TokenExchangeCompositeAuthenticationToken.class))
.isEqualTo(authentication);
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.List;
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.authentication.TestingAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* This mixin class is used to serialize/deserialize {@link TestingAuthenticationToken}.
*
* @author Steve Riesenberg
* @since 7.0
* @see TestingAuthenticationToken
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true)
public class TestingAuthenticationTokenMixin {
@JsonCreator
TestingAuthenticationTokenMixin(@JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") List<GrantedAuthority> authorities) {
}
}

View File

@ -15,6 +15,7 @@ dependencies {
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-r2dbc'
optional 'tools.jackson.core:jackson-databind'
testImplementation project(path: ':spring-security-oauth2-core', configuration: 'tests')
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')

View File

@ -0,0 +1,76 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
/**
* A {@code JsonDeserializer} for {@link ClientRegistration}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see ClientRegistration
* @see ClientRegistrationMixin
*/
final class ClientRegistrationDeserializer extends ValueDeserializer<ClientRegistration> {
private static final StdConverter<JsonNode, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_CONVERTER = new StdConverters.ClientAuthenticationMethodConverter();
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
private static final StdConverter<JsonNode, AuthenticationMethod> AUTHENTICATION_METHOD_CONVERTER = new StdConverters.AuthenticationMethodConverter();
@Override
public ClientRegistration deserialize(JsonParser parser, DeserializationContext context) {
JsonNode clientRegistrationNode = context.readTree(parser);
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
return ClientRegistration
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
.clientSecret(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret"))
.clientAuthenticationMethod(CLIENT_AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, context))
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
.userInfoUri(JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri"))
.userInfoAuthenticationMethod(AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(userInfoEndpointNode, "authenticationMethod")))
.userNameAttributeName(JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName"))
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
JsonNodeUtils.STRING_OBJECT_MAP, context))
.build();
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
/**
* This mixin class is used to serialize/deserialize {@link ClientRegistration}. It also
* registers a custom deserializer {@link ClientRegistrationDeserializer}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see ClientRegistration
* @see ClientRegistrationDeserializer
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = ClientRegistrationDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class ClientRegistrationMixin {
}

View File

@ -0,0 +1,50 @@
/*
* 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.oauth2.client.jackson;
import java.util.Collection;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
/**
* This mixin class is used to serialize/deserialize {@link DefaultOAuth2User}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see DefaultOAuth2User
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class DefaultOAuth2UserMixin {
@JsonCreator
DefaultOAuth2UserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("attributes") Map<String, Object> attributes,
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.oauth2.client.jackson;
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;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
/**
* This mixin class is used to serialize/deserialize {@link DefaultOidcUser}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see DefaultOidcUser
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "attributes" })
abstract class DefaultOidcUserMixin {
@JsonCreator
DefaultOidcUserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("idToken") OidcIdToken idToken, @JsonProperty("userInfo") OidcUserInfo userInfo,
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.oauth2.client.jackson;
import java.util.Map;
import java.util.Set;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
/**
* Utility class for {@code JsonNode}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isString()) ? value.stringValue() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
DeserializationContext context) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainer())
? context.readTreeAsValue(value, context.getTypeFactory().constructType(valueTypeReference)) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AccessToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AccessToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AccessTokenMixin {
@JsonCreator
OAuth2AccessTokenMixin(
@JsonProperty("tokenType") @JsonDeserialize(
converter = StdConverters.AccessTokenTypeConverter.class) OAuth2AccessToken.TokenType tokenType,
@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("scopes") Set<String> scopes) {
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.oauth2.client.jackson;
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.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
/**
* This mixin class is used to serialize/deserialize
* {@link OAuth2AuthenticationException}.
*
* @author Sebastien Deleuze
* @author Dennis Neufeld
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2AuthenticationException
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "cause", "stackTrace", "suppressedExceptions" })
abstract class OAuth2AuthenticationExceptionMixin {
@JsonProperty("error")
abstract OAuth2Error getError();
@JsonProperty("detailMessage")
abstract String getMessage();
@JsonCreator
OAuth2AuthenticationExceptionMixin(@JsonProperty("error") OAuth2Error error,
@JsonProperty("detailMessage") String message) {
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.oauth2.client.jackson;
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;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthenticationToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0O
* @see OAuth2AuthenticationToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "authenticated" })
abstract class OAuth2AuthenticationTokenMixin {
@JsonCreator
OAuth2AuthenticationTokenMixin(@JsonProperty("principal") OAuth2User principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("authorizedClientRegistrationId") String authorizedClientRegistrationId) {
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestMixin
*/
final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAuth2AuthorizationRequest> {
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
@Override
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) {
JsonNode root = context.readTree(parser);
return deserialize(parser, context, root);
}
private OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context, JsonNode root) {
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, context));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, context));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, context));
return builder.build();
}
private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.authorizationCode();
}
throw new StreamReadException(parser, "Invalid authorizationGrantType");
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
* It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestDeserializer
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizationRequestMixin {
}

View File

@ -0,0 +1,50 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizedClient}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizedClient
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizedClientMixin {
@JsonCreator
OAuth2AuthorizedClientMixin(@JsonProperty("clientRegistration") ClientRegistration clientRegistration,
@JsonProperty("principalName") String principalName,
@JsonProperty("accessToken") OAuth2AccessToken accessToken,
@JsonProperty("refreshToken") OAuth2RefreshToken refreshToken) {
}
}

View File

@ -0,0 +1,118 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.jackson.SecurityJacksonModule;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
/**
* Jackson {@code Module} for {@code spring-security-oauth2-client}, that registers the
* following mix-in annotations:
*
* <ul>
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
* <li>{@link ClientRegistrationMixin}</li>
* <li>{@link OAuth2AccessTokenMixin}</li>
* <li>{@link OAuth2RefreshTokenMixin}</li>
* <li>{@link OAuth2AuthorizedClientMixin}</li>
* <li>{@link OAuth2UserAuthorityMixin}</li>
* <li>{@link DefaultOAuth2UserMixin}</li>
* <li>{@link OidcIdTokenMixin}</li>
* <li>{@link OidcUserInfoMixin}</li>
* <li>{@link OidcUserAuthorityMixin}</li>
* <li>{@link DefaultOidcUserMixin}</li>
* <li>{@link OAuth2AuthenticationTokenMixin}</li>
* <li>{@link OAuth2AuthenticationExceptionMixin}</li>
* <li>{@link OAuth2ErrorMixin}</li>
* </ul>
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
@SuppressWarnings("serial")
public class OAuth2ClientJacksonModule extends SecurityJacksonModule {
public OAuth2ClientJacksonModule() {
super(OAuth2ClientJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(OAuth2AuthenticationException.class)
.allowIfSubType(DefaultOidcUser.class)
.allowIfSubType(OAuth2AuthorizationRequest.class)
.allowIfSubType(OAuth2Error.class)
.allowIfSubType(OAuth2AuthorizedClient.class)
.allowIfSubType(OidcIdToken.class)
.allowIfSubType(OidcUserInfo.class)
.allowIfSubType(DefaultOAuth2User.class)
.allowIfSubType(ClientRegistration.class)
.allowIfSubType(OAuth2AccessToken.class)
.allowIfSubType(OAuth2RefreshToken.class)
.allowIfSubType(OAuth2AuthenticationToken.class)
.allowIfSubType(OidcUserAuthority.class)
.allowIfSubType(OAuth2UserAuthority.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
context.setMixIn(ClientRegistration.class, ClientRegistrationMixin.class);
context.setMixIn(OAuth2AccessToken.class, OAuth2AccessTokenMixin.class);
context.setMixIn(OAuth2RefreshToken.class, OAuth2RefreshTokenMixin.class);
context.setMixIn(OAuth2AuthorizedClient.class, OAuth2AuthorizedClientMixin.class);
context.setMixIn(OAuth2UserAuthority.class, OAuth2UserAuthorityMixin.class);
context.setMixIn(DefaultOAuth2User.class, DefaultOAuth2UserMixin.class);
context.setMixIn(OidcIdToken.class, OidcIdTokenMixin.class);
context.setMixIn(OidcUserInfo.class, OidcUserInfoMixin.class);
context.setMixIn(OidcUserAuthority.class, OidcUserAuthorityMixin.class);
context.setMixIn(DefaultOidcUser.class, DefaultOidcUserMixin.class);
context.setMixIn(OAuth2AuthenticationToken.class, OAuth2AuthenticationTokenMixin.class);
context.setMixIn(OAuth2AuthenticationException.class, OAuth2AuthenticationExceptionMixin.class);
context.setMixIn(OAuth2Error.class, OAuth2ErrorMixin.class);
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.OAuth2Error;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2Error} as part of
* {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException}.
*
* @author Sebastien Deleuze
* @author Dennis Neufeld
* @since 7.0
* @see OAuth2Error
* @see OAuth2AuthenticationExceptionMixin
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2ErrorMixin {
@JsonCreator
OAuth2ErrorMixin(@JsonProperty("errorCode") String errorCode, @JsonProperty("description") String description,
@JsonProperty("uri") String uri) {
}
}

View File

@ -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.oauth2.client.jackson;
import java.time.Instant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2RefreshToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2RefreshToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2RefreshTokenMixin {
@JsonCreator
OAuth2RefreshTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt) {
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.oauth2.client.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2UserAuthority}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2UserAuthority
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2UserAuthorityMixin {
@JsonCreator
OAuth2UserAuthorityMixin(@JsonProperty("authority") String authority,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
/**
* This mixin class is used to serialize/deserialize {@link OidcIdToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcIdToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OidcIdTokenMixin {
@JsonCreator
OidcIdTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("claims") Map<String, Object> claims) {
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.oauth2.client.jackson;
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.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
/**
* This mixin class is used to serialize/deserialize {@link OidcUserAuthority}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcUserAuthority
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "attributes" })
abstract class OidcUserAuthorityMixin {
@JsonCreator
OidcUserAuthorityMixin(@JsonProperty("authority") String authority, @JsonProperty("idToken") OidcIdToken idToken,
@JsonProperty("userInfo") OidcUserInfo userInfo) {
}
}

View File

@ -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.oauth2.client.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
/**
* This mixin class is used to serialize/deserialize {@link OidcUserInfo}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcUserInfo
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OidcUserInfoMixin {
@JsonCreator
OidcUserInfoMixin(@JsonProperty("claims") Map<String, Object> claims) {
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
/**
* {@code StdConverter} implementations.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
abstract class StdConverters {
static final class AccessTokenTypeConverter extends StdConverter<JsonNode, OAuth2AccessToken.TokenType> {
@Override
public OAuth2AccessToken.TokenType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
return OAuth2AccessToken.TokenType.BEARER;
}
return null;
}
}
static final class ClientAuthenticationMethodConverter extends StdConverter<JsonNode, ClientAuthenticationMethod> {
@Override
public ClientAuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
return ClientAuthenticationMethod.valueOf(value);
}
}
static final class AuthorizationGrantTypeConverter extends StdConverter<JsonNode, AuthorizationGrantType> {
@Override
public AuthorizationGrantType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
}
return new AuthorizationGrantType(value);
}
}
static final class AuthenticationMethodConverter extends StdConverter<JsonNode, AuthenticationMethod> {
@Override
public AuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.HEADER;
}
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.FORM;
}
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.QUERY;
}
return null;
}
}
}

View File

@ -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 OAuth2 client.
*/
package org.springframework.security.oauth2.client.jackson;

View File

@ -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 2 serialization support for OAuth2 client.
*/
package org.springframework.security.oauth2.client.jackson2;

View File

@ -0,0 +1,129 @@
/*
* 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.oauth2.client.jackson;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.ValueInstantiationException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationExceptionMixin}.
*
* @author Dennis Neufeld
* @since 5.3.4
*/
public class OAuth2AuthenticationExceptionMixinTests {
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]", "Authorization Request Not Found", "/foo/bar"),
"Authorization Request Not Found");
String serializedJson = this.mapper.writeValueAsString(exception);
String expected = asJson(exception);
JSONAssert.assertEquals(expected, serializedJson, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]"));
String serializedJson = this.mapper.writeValueAsString(exception);
String expected = asJson(exception);
JSONAssert.assertEquals(expected, serializedJson, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
String json = asJson(new OAuth2AuthenticationException(new OAuth2Error("[authorization_request_not_found]")));
assertThatExceptionOfType(ValueInstantiationException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthenticationException.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
OAuth2AuthenticationException expected = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]", "Authorization Request Not Found", "/foo/bar"),
"Authorization Request Not Found");
OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected),
OAuth2AuthenticationException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isEqualTo(expected.getMessage());
OAuth2Error oauth2Error = exception.getError();
assertThat(oauth2Error).isNotNull();
assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode());
assertThat(oauth2Error.getDescription()).isEqualTo(expected.getError().getDescription());
assertThat(oauth2Error.getUri()).isEqualTo(expected.getError().getUri());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
OAuth2AuthenticationException expected = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]"));
OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected),
OAuth2AuthenticationException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isNull();
OAuth2Error oauth2Error = exception.getError();
assertThat(oauth2Error).isNotNull();
assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode());
assertThat(oauth2Error.getDescription()).isNull();
assertThat(oauth2Error.getUri()).isNull();
}
private String asJson(OAuth2AuthenticationException exception) {
OAuth2Error error = exception.getError();
// @formatter:off
return "\n{"
+ "\n \"@class\": \"org.springframework.security.oauth2.core.OAuth2AuthenticationException\","
+ "\n \"error\":"
+ "\n {"
+ "\n \"@class\":\"org.springframework.security.oauth2.core.OAuth2Error\","
+ "\n \"errorCode\":\"" + error.getErrorCode() + "\","
+ "\n \"description\":" + jsonStringOrNull(error.getDescription()) + ","
+ "\n \"uri\":" + jsonStringOrNull(error.getUri())
+ "\n },"
+ "\n \"detailMessage\":" + jsonStringOrNull(exception.getMessage())
+ "\n}";
// @formatter:on
}
private String jsonStringOrNull(String input) {
return (input != null) ? "\"" + input + "\"" : "null";
}
}

View File

@ -0,0 +1,339 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthenticationTokens;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationTokenMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthenticationTokenMixinTests {
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder()
.addModules(SecurityJacksonModules.getModules(loader))
// see https://github.com/FasterXML/jackson-databind/issues/3052 for details
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
// OidcUser
OAuth2AuthenticationToken authentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String expectedJson = asJson(authentication);
String json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
// OAuth2User
authentication = TestOAuth2AuthenticationTokens.authenticated();
expectedJson = asJson(authentication);
json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
DefaultOidcUser principal = TestOidcUsers.create();
principal = new DefaultOidcUser(principal.getAuthorities(), principal.getIdToken());
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(principal, Collections.emptyList(),
"registration-id");
String expectedJson = asJson(authentication);
String json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
OAuth2AuthenticationToken authentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String json = asJson(authentication);
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthenticationToken.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
// OidcUser
OAuth2AuthenticationToken expectedAuthentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String json = asJson(expectedAuthentication);
OAuth2AuthenticationToken authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOidcUser expectedOidcUser = (DefaultOidcUser) expectedAuthentication.getPrincipal();
DefaultOidcUser oidcUser = (DefaultOidcUser) authentication.getPrincipal();
assertThat(oidcUser.getAuthorities().containsAll(expectedOidcUser.getAuthorities())).isTrue();
assertThat(oidcUser.getAttributes()).containsExactlyEntriesOf(expectedOidcUser.getAttributes());
assertThat(oidcUser.getName()).isEqualTo(expectedOidcUser.getName());
OidcIdToken expectedIdToken = expectedOidcUser.getIdToken();
OidcIdToken idToken = oidcUser.getIdToken();
assertThat(idToken.getTokenValue()).isEqualTo(expectedIdToken.getTokenValue());
assertThat(idToken.getIssuedAt()).isEqualTo(expectedIdToken.getIssuedAt());
assertThat(idToken.getExpiresAt()).isEqualTo(expectedIdToken.getExpiresAt());
assertThat(idToken.getClaims()).containsExactlyEntriesOf(expectedIdToken.getClaims());
OidcUserInfo expectedUserInfo = expectedOidcUser.getUserInfo();
OidcUserInfo userInfo = oidcUser.getUserInfo();
assertThat(userInfo.getClaims()).containsExactlyEntriesOf(expectedUserInfo.getClaims());
// OAuth2User
expectedAuthentication = TestOAuth2AuthenticationTokens.authenticated();
json = asJson(expectedAuthentication);
authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOAuth2User expectedOauth2User = (DefaultOAuth2User) expectedAuthentication.getPrincipal();
DefaultOAuth2User oauth2User = (DefaultOAuth2User) authentication.getPrincipal();
assertThat(oauth2User.getAuthorities().containsAll(expectedOauth2User.getAuthorities())).isTrue();
assertThat(oauth2User.getAttributes()).containsExactlyEntriesOf(expectedOauth2User.getAttributes());
assertThat(oauth2User.getName()).isEqualTo(expectedOauth2User.getName());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
DefaultOidcUser expectedPrincipal = TestOidcUsers.create();
expectedPrincipal = new DefaultOidcUser(expectedPrincipal.getAuthorities(), expectedPrincipal.getIdToken());
OAuth2AuthenticationToken expectedAuthentication = new OAuth2AuthenticationToken(expectedPrincipal,
Collections.emptyList(), "registration-id");
String json = asJson(expectedAuthentication);
OAuth2AuthenticationToken authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).isEmpty();
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOidcUser principal = (DefaultOidcUser) authentication.getPrincipal();
assertThat(principal.getAuthorities().containsAll(expectedPrincipal.getAuthorities())).isTrue();
assertThat(principal.getAttributes()).containsExactlyEntriesOf(expectedPrincipal.getAttributes());
assertThat(principal.getName()).isEqualTo(expectedPrincipal.getName());
OidcIdToken expectedIdToken = expectedPrincipal.getIdToken();
OidcIdToken idToken = principal.getIdToken();
assertThat(idToken.getTokenValue()).isEqualTo(expectedIdToken.getTokenValue());
assertThat(idToken.getIssuedAt()).isEqualTo(expectedIdToken.getIssuedAt());
assertThat(idToken.getExpiresAt()).isEqualTo(expectedIdToken.getExpiresAt());
assertThat(idToken.getClaims()).containsExactlyEntriesOf(expectedIdToken.getClaims());
assertThat(principal.getUserInfo()).isNull();
}
private static String asJson(OAuth2AuthenticationToken authentication) {
String principalJson = (authentication.getPrincipal() instanceof DefaultOidcUser)
? asJson((DefaultOidcUser) authentication.getPrincipal())
: asJson((DefaultOAuth2User) authentication.getPrincipal());
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken\",\n" +
" \"principal\": " + principalJson + ",\n" +
" \"authorities\": " + asJson(authentication.getAuthorities(), "java.util.Collections$UnmodifiableRandomAccessList") + ",\n" +
" \"authorizedClientRegistrationId\": \"" + authentication.getAuthorizedClientRegistrationId() + "\",\n" +
" \"details\": null\n" +
"}";
// @formatter:on
}
private static String asJson(DefaultOAuth2User oauth2User) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.user.DefaultOAuth2User\",\n" +
" \"authorities\": " + asJson(oauth2User.getAuthorities(), "java.util.Collections$UnmodifiableSet") + ",\n" +
" \"attributes\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"username\": \"user\"\n" +
" },\n" +
" \"nameAttributeKey\": \"username\"\n" +
" }";
// @formatter:on
}
private static String asJson(DefaultOidcUser oidcUser) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser\",\n" +
" \"authorities\": " + asJson(oidcUser.getAuthorities(), "java.util.Collections$UnmodifiableSet") + ",\n" +
" \"idToken\": " + asJson(oidcUser.getIdToken()) + ",\n" +
" \"userInfo\": " + asJson(oidcUser.getUserInfo()) + ",\n" +
" \"nameAttributeKey\": \"" + IdTokenClaimNames.SUB + "\"\n" +
" }";
// @formatter:on
}
private static String asJson(Collection<? extends GrantedAuthority> authorities, String classTypeInfo) {
OAuth2UserAuthority oauth2UserAuthority = null;
OidcUserAuthority oidcUserAuthority = null;
List<SimpleGrantedAuthority> simpleAuthorities = new ArrayList<>();
for (GrantedAuthority authority : authorities) {
if (authority instanceof OidcUserAuthority) {
oidcUserAuthority = (OidcUserAuthority) authority;
}
else if (authority instanceof OAuth2UserAuthority) {
oauth2UserAuthority = (OAuth2UserAuthority) authority;
}
else if (authority instanceof SimpleGrantedAuthority) {
simpleAuthorities.add((SimpleGrantedAuthority) authority);
}
}
String authoritiesJson = (oidcUserAuthority != null) ? asJson(oidcUserAuthority)
: (oauth2UserAuthority != null) ? asJson(oauth2UserAuthority) : "";
if (!simpleAuthorities.isEmpty()) {
if (StringUtils.hasLength(authoritiesJson)) {
authoritiesJson += ",";
}
authoritiesJson += asJson(simpleAuthorities);
}
// @formatter:off
return "[\n" +
" \"" + classTypeInfo + "\",\n" +
" [" + authoritiesJson + "]\n" +
" ]";
// @formatter:on
}
private static String asJson(OAuth2UserAuthority oauth2UserAuthority) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.user.OAuth2UserAuthority\",\n" +
" \"authority\": \"" + oauth2UserAuthority.getAuthority() + "\",\n" +
" \"userNameAttributeName\": \"username\",\n" +
" \"attributes\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"username\": \"user\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String asJson(OidcUserAuthority oidcUserAuthority) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority\",\n" +
" \"authority\": \"" + oidcUserAuthority.getAuthority() + "\",\n" +
" \"userNameAttributeName\": \"" + oidcUserAuthority.getUserNameAttributeName() + "\",\n" +
" \"idToken\": " + asJson(oidcUserAuthority.getIdToken()) + ",\n" +
" \"userInfo\": " + asJson(oidcUserAuthority.getUserInfo()) + "\n" +
" }";
// @formatter:on
}
private static String asJson(List<SimpleGrantedAuthority> simpleAuthorities) {
// @formatter:off
return simpleAuthorities.stream()
.map((authority) -> "{\n" +
" \"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\",\n" +
" \"authority\": \"" + authority.getAuthority() + "\"\n" +
" }")
.collect(Collectors.joining(","));
// @formatter:on
}
private static String asJson(OidcIdToken idToken) {
String aud = "";
if (!CollectionUtils.isEmpty(idToken.getAudience())) {
aud = StringUtils.collectionToDelimitedString(idToken.getAudience(), ",", "\"", "\"");
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.OidcIdToken\",\n" +
" \"tokenValue\": \"" + idToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(idToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(idToken.getExpiresAt()) + ",\n" +
" \"claims\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"iat\": [\n" +
" \"java.time.Instant\",\n" +
" " + toString(idToken.getIssuedAt()) + "\n" +
" ],\n" +
" \"exp\": [\n" +
" \"java.time.Instant\",\n" +
" " + toString(idToken.getExpiresAt()) + "\n" +
" ],\n" +
" \"sub\": \"" + idToken.getSubject() + "\",\n" +
" \"iss\": \"" + idToken.getIssuer() + "\",\n" +
" \"aud\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + aud + "]\n" +
" ],\n" +
" \"azp\": \"" + idToken.getAuthorizedParty() + "\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String asJson(OidcUserInfo userInfo) {
if (userInfo == null) {
return null;
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.OidcUserInfo\",\n" +
" \"claims\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"sub\": \"" + userInfo.getSubject() + "\",\n" +
" \"name\": \"" + userInfo.getClaim(StandardClaimNames.NAME) + "\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String toString(Instant instant) {
if (instant == null) {
return null;
}
return DecimalUtils.toBigDecimal(instant.getEpochSecond(), instant.getNano()).toString();
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.oauth2.client.jackson;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthorizationRequestMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthorizationRequestMixinTests {
private JsonMapper mapper;
private OAuth2AuthorizationRequest.Builder authorizationRequestBuilder;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("param1", "value1");
additionalParameters.put("param2", "value2");
// @formatter:off
this.authorizationRequestBuilder = TestOAuth2AuthorizationRequests.request()
.scope("read", "write")
.additionalParameters(additionalParameters);
// @formatter:on
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder.build();
String expectedJson = asJson(authorizationRequest);
String json = this.mapper.writeValueAsString(authorizationRequest);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
// @formatter:off
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder
.scopes(null)
.state(null)
.additionalParameters(Map::clear)
.attributes(Map::clear)
.build();
// @formatter:on
String expectedJson = asJson(authorizationRequest);
String json = this.mapper.writeValueAsString(authorizationRequest);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
String json = asJson(this.authorizationRequestBuilder.build());
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthorizationRequest.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
OAuth2AuthorizationRequest expectedAuthorizationRequest = this.authorizationRequestBuilder.build();
String json = asJson(expectedAuthorizationRequest);
OAuth2AuthorizationRequest authorizationRequest = this.mapper.readValue(json, OAuth2AuthorizationRequest.class);
assertThat(authorizationRequest.getAuthorizationUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationUri());
assertThat(authorizationRequest.getGrantType()).isEqualTo(expectedAuthorizationRequest.getGrantType());
assertThat(authorizationRequest.getResponseType()).isEqualTo(expectedAuthorizationRequest.getResponseType());
assertThat(authorizationRequest.getClientId()).isEqualTo(expectedAuthorizationRequest.getClientId());
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedAuthorizationRequest.getRedirectUri());
assertThat(authorizationRequest.getScopes()).isEqualTo(expectedAuthorizationRequest.getScopes());
assertThat(authorizationRequest.getState()).isEqualTo(expectedAuthorizationRequest.getState());
assertThat(authorizationRequest.getAdditionalParameters())
.containsExactlyEntriesOf(expectedAuthorizationRequest.getAdditionalParameters());
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationRequestUri());
assertThat(authorizationRequest.getAttributes())
.containsExactlyEntriesOf(expectedAuthorizationRequest.getAttributes());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
// @formatter:off
OAuth2AuthorizationRequest expectedAuthorizationRequest = this.authorizationRequestBuilder.scopes(null)
.state(null)
.additionalParameters(Map::clear)
.attributes(Map::clear)
.build();
// @formatter:on
String json = asJson(expectedAuthorizationRequest);
OAuth2AuthorizationRequest authorizationRequest = this.mapper.readValue(json, OAuth2AuthorizationRequest.class);
assertThat(authorizationRequest.getAuthorizationUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationUri());
assertThat(authorizationRequest.getGrantType()).isEqualTo(expectedAuthorizationRequest.getGrantType());
assertThat(authorizationRequest.getResponseType()).isEqualTo(expectedAuthorizationRequest.getResponseType());
assertThat(authorizationRequest.getClientId()).isEqualTo(expectedAuthorizationRequest.getClientId());
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedAuthorizationRequest.getRedirectUri());
assertThat(authorizationRequest.getScopes()).isEmpty();
assertThat(authorizationRequest.getState()).isNull();
assertThat(authorizationRequest.getAdditionalParameters()).isEmpty();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationRequestUri());
assertThat(authorizationRequest.getAttributes()).isEmpty();
}
@Test
public void deserializeWhenInvalidAuthorizationGrantTypeThenThrowJsonParseException() {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder.build();
String json = asJson(authorizationRequest).replace("authorization_code", "client_credentials");
assertThatExceptionOfType(StreamReadException.class)
.isThrownBy(() -> this.mapper.readValue(json, OAuth2AuthorizationRequest.class))
.withMessageContaining("Invalid authorizationGrantType");
}
private static String asJson(OAuth2AuthorizationRequest authorizationRequest) {
String scopes = "";
if (!CollectionUtils.isEmpty(authorizationRequest.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(authorizationRequest.getScopes(), ",", "\"", "\"");
}
String additionalParameters = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(authorizationRequest.getAdditionalParameters())) {
additionalParameters += "," + authorizationRequest.getAdditionalParameters()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + authorizationRequest.getAdditionalParameters().get(key) + "\"")
.collect(Collectors.joining(","));
}
String attributes = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(authorizationRequest.getAttributes())) {
attributes += "," + authorizationRequest.getAttributes()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + authorizationRequest.getAttributes().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest\",\n" +
" \"authorizationUri\": \"" + authorizationRequest.getAuthorizationUri() + "\",\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + authorizationRequest.getGrantType().getValue() + "\"\n" +
" },\n" +
" \"responseType\": {\n" +
" \"value\": \"" + authorizationRequest.getResponseType().getValue() + "\"\n" +
" },\n" +
" \"clientId\": \"" + authorizationRequest.getClientId() + "\",\n" +
" \"redirectUri\": \"" + authorizationRequest.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"state\": " + ((authorizationRequest.getState() != null) ? "\"" + authorizationRequest.getState() + "\"" : "null") + ",\n" +
" \"additionalParameters\": {\n" +
" " + additionalParameters + "\n" +
" },\n" +
" \"authorizationRequestUri\": \"" + authorizationRequest.getAuthorizationRequestUri() + "\",\n" +
" \"attributes\": {\n" +
" " + attributes + "\n" +
" }\n" +
"}";
// @formatter:on
}
}

View File

@ -0,0 +1,395 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthorizedClientMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthorizedClientMixinTests {
private JsonMapper mapper;
private ClientRegistration.Builder clientRegistrationBuilder;
private OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken;
private String principalName;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
Map<String, Object> providerConfigurationMetadata = new LinkedHashMap<>();
providerConfigurationMetadata.put("config1", "value1");
providerConfigurationMetadata.put("config2", "value2");
// @formatter:off
this.clientRegistrationBuilder = TestClientRegistrations.clientRegistration()
.authorizationGrantType(new AuthorizationGrantType("custom-grant"))
.scope("read", "write")
.providerConfigurationMetadata(providerConfigurationMetadata);
// @formatter:on
this.accessToken = TestOAuth2AccessTokens.scopes("read", "write");
this.refreshToken = TestOAuth2RefreshTokens.refreshToken();
this.principalName = "principal-name";
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistrationBuilder.build(),
this.principalName, this.accessToken, this.refreshToken);
String expectedJson = asJson(authorizedClient);
String json = this.mapper.writeValueAsString(authorizedClient);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
// @formatter:off
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
.clientSecret(null)
.clientName(null)
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
.issuerUri(null)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, this.principalName,
TestOAuth2AccessTokens.noScopes());
String expectedJson = asJson(authorizedClient);
String json = this.mapper.writeValueAsString(authorizedClient);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistrationBuilder.build(),
this.principalName, this.accessToken);
String json = asJson(authorizedClient);
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthorizedClient.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
ClientRegistration expectedClientRegistration = this.clientRegistrationBuilder.build();
OAuth2AccessToken expectedAccessToken = this.accessToken;
OAuth2RefreshToken expectedRefreshToken = this.refreshToken;
OAuth2AuthorizedClient expectedAuthorizedClient = new OAuth2AuthorizedClient(expectedClientRegistration,
this.principalName, expectedAccessToken, expectedRefreshToken);
String json = asJson(expectedAuthorizedClient);
OAuth2AuthorizedClient authorizedClient = this.mapper.readValue(json, OAuth2AuthorizedClient.class);
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
assertThat(clientRegistration.getRegistrationId()).isEqualTo(expectedClientRegistration.getRegistrationId());
assertThat(clientRegistration.getClientId()).isEqualTo(expectedClientRegistration.getClientId());
assertThat(clientRegistration.getClientSecret()).isEqualTo(expectedClientRegistration.getClientSecret());
assertThat(clientRegistration.getClientAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getClientAuthenticationMethod());
assertThat(clientRegistration.getAuthorizationGrantType())
.isEqualTo(expectedClientRegistration.getAuthorizationGrantType());
assertThat(clientRegistration.getRedirectUri()).isEqualTo(expectedClientRegistration.getRedirectUri());
assertThat(clientRegistration.getScopes()).isEqualTo(expectedClientRegistration.getScopes());
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getAuthorizationUri());
assertThat(clientRegistration.getProviderDetails().getTokenUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getTokenUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(
expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
assertThat(clientRegistration.getProviderDetails().getJwkSetUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getJwkSetUri());
assertThat(clientRegistration.getProviderDetails().getIssuerUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getIssuerUri());
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata())
.containsExactlyEntriesOf(clientRegistration.getProviderDetails().getConfigurationMetadata());
assertThat(clientRegistration.getClientName()).isEqualTo(expectedClientRegistration.getClientName());
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expectedAuthorizedClient.getPrincipalName());
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
assertThat(accessToken.getTokenType()).isEqualTo(expectedAccessToken.getTokenType());
assertThat(accessToken.getScopes()).isEqualTo(expectedAccessToken.getScopes());
assertThat(accessToken.getTokenValue()).isEqualTo(expectedAccessToken.getTokenValue());
assertThat(accessToken.getIssuedAt()).isEqualTo(expectedAccessToken.getIssuedAt());
assertThat(accessToken.getExpiresAt()).isEqualTo(expectedAccessToken.getExpiresAt());
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
assertThat(refreshToken.getTokenValue()).isEqualTo(expectedRefreshToken.getTokenValue());
assertThat(refreshToken.getIssuedAt()).isEqualTo(expectedRefreshToken.getIssuedAt());
assertThat(refreshToken.getExpiresAt()).isEqualTo(expectedRefreshToken.getExpiresAt());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
// @formatter:off
ClientRegistration expectedClientRegistration = TestClientRegistrations.clientRegistration()
.clientSecret(null)
.clientName(null)
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
.issuerUri(null)
.build();
// @formatter:on
OAuth2AccessToken expectedAccessToken = TestOAuth2AccessTokens.noScopes();
OAuth2AuthorizedClient expectedAuthorizedClient = new OAuth2AuthorizedClient(expectedClientRegistration,
this.principalName, expectedAccessToken);
String json = asJson(expectedAuthorizedClient);
OAuth2AuthorizedClient authorizedClient = this.mapper.readValue(json, OAuth2AuthorizedClient.class);
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
assertThat(clientRegistration.getRegistrationId()).isEqualTo(expectedClientRegistration.getRegistrationId());
assertThat(clientRegistration.getClientId()).isEqualTo(expectedClientRegistration.getClientId());
assertThat(clientRegistration.getClientSecret()).isEmpty();
assertThat(clientRegistration.getClientAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getClientAuthenticationMethod());
assertThat(clientRegistration.getAuthorizationGrantType())
.isEqualTo(expectedClientRegistration.getAuthorizationGrantType());
assertThat(clientRegistration.getRedirectUri()).isEqualTo(expectedClientRegistration.getRedirectUri());
assertThat(clientRegistration.getScopes()).isEqualTo(expectedClientRegistration.getScopes());
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getAuthorizationUri());
assertThat(clientRegistration.getProviderDetails().getTokenUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getTokenUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isNull();
assertThat(clientRegistration.getProviderDetails().getJwkSetUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty();
assertThat(clientRegistration.getClientName()).isEqualTo(clientRegistration.getRegistrationId());
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expectedAuthorizedClient.getPrincipalName());
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
assertThat(accessToken.getTokenType()).isEqualTo(expectedAccessToken.getTokenType());
assertThat(accessToken.getScopes()).isEmpty();
assertThat(accessToken.getTokenValue()).isEqualTo(expectedAccessToken.getTokenValue());
assertThat(accessToken.getIssuedAt()).isEqualTo(expectedAccessToken.getIssuedAt());
assertThat(accessToken.getExpiresAt()).isEqualTo(expectedAccessToken.getExpiresAt());
assertThat(authorizedClient.getRefreshToken()).isNull();
}
@Test
void deserializeWhenClientSettingsPropertyDoesNotExistThenDefaulted() throws JacksonException {
// ClientRegistration.clientSettings was added later, so old values will be
// serialized without that property
// this test checks for passivity
ClientRegistration clientRegistration = this.clientRegistrationBuilder.build();
ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
String scopes = "";
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), ",", "\"", "\"");
}
String configurationMetadata = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(providerDetails.getConfigurationMetadata())) {
configurationMetadata += "," + providerDetails.getConfigurationMetadata()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + providerDetails.getConfigurationMetadata().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
String json = "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration\",\n" +
" \"registrationId\": \"" + clientRegistration.getRegistrationId() + "\",\n" +
" \"clientId\": \"" + clientRegistration.getClientId() + "\",\n" +
" \"clientSecret\": \"" + clientRegistration.getClientSecret() + "\",\n" +
" \"clientAuthenticationMethod\": {\n" +
" \"value\": \"" + clientRegistration.getClientAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + clientRegistration.getAuthorizationGrantType().getValue() + "\"\n" +
" },\n" +
" \"redirectUri\": \"" + clientRegistration.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"providerDetails\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails\",\n" +
" \"authorizationUri\": \"" + providerDetails.getAuthorizationUri() + "\",\n" +
" \"tokenUri\": \"" + providerDetails.getTokenUri() + "\",\n" +
" \"userInfoEndpoint\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails$UserInfoEndpoint\",\n" +
" \"uri\": " + ((userInfoEndpoint.getUri() != null) ? "\"" + userInfoEndpoint.getUri() + "\"" : null) + ",\n" +
" \"authenticationMethod\": {\n" +
" \"value\": \"" + userInfoEndpoint.getAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"userNameAttributeName\": " + ((userInfoEndpoint.getUserNameAttributeName() != null) ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
" },\n" +
" \"jwkSetUri\": " + ((providerDetails.getJwkSetUri() != null) ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
" \"issuerUri\": " + ((providerDetails.getIssuerUri() != null) ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
" \"configurationMetadata\": {\n" +
" " + configurationMetadata + "\n" +
" }\n" +
" },\n" +
" \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" +
"}";
// @formatter:on
// validate the test input
assertThat(json).doesNotContain("clientSettings");
ClientRegistration registration = this.mapper.readValue(json, ClientRegistration.class);
// the default value of requireProofKey is false
assertThat(registration.getClientSettings().isRequireProofKey()).isFalse();
}
private static String asJson(OAuth2AuthorizedClient authorizedClient) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.OAuth2AuthorizedClient\",\n" +
" \"clientRegistration\": " + asJson(authorizedClient.getClientRegistration()) + ",\n" +
" \"principalName\": \"" + authorizedClient.getPrincipalName() + "\",\n" +
" \"accessToken\": " + asJson(authorizedClient.getAccessToken()) + ",\n" +
" \"refreshToken\": " + asJson(authorizedClient.getRefreshToken()) + "\n" +
"}";
// @formatter:on
}
private static String asJson(ClientRegistration clientRegistration) {
ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
String scopes = "";
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), ",", "\"", "\"");
}
String configurationMetadata = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(providerDetails.getConfigurationMetadata())) {
configurationMetadata += "," + providerDetails.getConfigurationMetadata()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + providerDetails.getConfigurationMetadata().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration\",\n" +
" \"registrationId\": \"" + clientRegistration.getRegistrationId() + "\",\n" +
" \"clientId\": \"" + clientRegistration.getClientId() + "\",\n" +
" \"clientSecret\": \"" + clientRegistration.getClientSecret() + "\",\n" +
" \"clientAuthenticationMethod\": {\n" +
" \"value\": \"" + clientRegistration.getClientAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + clientRegistration.getAuthorizationGrantType().getValue() + "\"\n" +
" },\n" +
" \"redirectUri\": \"" + clientRegistration.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"providerDetails\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails\",\n" +
" \"authorizationUri\": \"" + providerDetails.getAuthorizationUri() + "\",\n" +
" \"tokenUri\": \"" + providerDetails.getTokenUri() + "\",\n" +
" \"userInfoEndpoint\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails$UserInfoEndpoint\",\n" +
" \"uri\": " + ((userInfoEndpoint.getUri() != null) ? "\"" + userInfoEndpoint.getUri() + "\"" : null) + ",\n" +
" \"authenticationMethod\": {\n" +
" \"value\": \"" + userInfoEndpoint.getAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"userNameAttributeName\": " + ((userInfoEndpoint.getUserNameAttributeName() != null) ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
" },\n" +
" \"jwkSetUri\": " + ((providerDetails.getJwkSetUri() != null) ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
" \"issuerUri\": " + ((providerDetails.getIssuerUri() != null) ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
" \"configurationMetadata\": {\n" +
" " + configurationMetadata + "\n" +
" }\n" +
" },\n" +
" \"clientName\": \"" + clientRegistration.getClientName() + "\",\n" +
" \"clientSettings\": {\n" +
" \"requireProofKey\": " + clientRegistration.getClientSettings().isRequireProofKey() + "\n" +
" }\n" +
"}";
// @formatter:on
}
private static String asJson(OAuth2AccessToken accessToken) {
String scopes = "";
if (!CollectionUtils.isEmpty(accessToken.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(accessToken.getScopes(), ",", "\"", "\"");
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.OAuth2AccessToken\",\n" +
" \"tokenType\": {\n" +
" \"value\": \"" + accessToken.getTokenType().getValue() + "\"\n" +
" },\n" +
" \"tokenValue\": \"" + accessToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(accessToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(accessToken.getExpiresAt()) + ",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ]\n" +
"}";
// @formatter:on
}
private static String asJson(OAuth2RefreshToken refreshToken) {
if (refreshToken == null) {
return null;
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.OAuth2RefreshToken\",\n" +
" \"tokenValue\": \"" + refreshToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(refreshToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(refreshToken.getExpiresAt()) + "\n" +
"}";
// @formatter:on
}
private static String toString(Instant instant) {
if (instant == null) {
return null;
}
return DecimalUtils.toBigDecimal(instant.getEpochSecond(), instant.getNano()).toString();
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.oauth2.client.jackson;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import static org.assertj.core.api.Assertions.assertThat;
public class StdConvertersTests {
private final StdConverter<JsonNode, ClientAuthenticationMethod> clientAuthenticationMethodConverter = new org.springframework.security.oauth2.client.jackson.StdConverters.ClientAuthenticationMethodConverter();
@ParameterizedTest
@MethodSource("convertWhenClientAuthenticationMethodConvertedThenDeserializes")
void convertWhenClientAuthenticationMethodConvertedThenDeserializes(String clientAuthenticationMethod) {
ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
jsonNode.put("value", clientAuthenticationMethod);
ClientAuthenticationMethod actual = this.clientAuthenticationMethodConverter.convert(jsonNode);
assertThat(actual.getValue()).isEqualTo(clientAuthenticationMethod);
}
static Stream<Arguments> convertWhenClientAuthenticationMethodConvertedThenDeserializes() {
return Stream.of(Arguments.of(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()),
Arguments.of(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()),
Arguments.of(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()),
Arguments.of(ClientAuthenticationMethod.NONE.getValue()), Arguments.of("custom_method"));
}
}

View File

@ -20,8 +20,6 @@ import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@ -29,6 +27,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -111,7 +111,7 @@ public class ClientRegistrationsTests {
private MockWebServer server;
private ObjectMapper mapper = new ObjectMapper();
private JsonMapper mapper = new JsonMapper();
private Map<String, Object> response;

View File

@ -15,7 +15,7 @@ dependencies {
testImplementation "jakarta.servlet:jakarta.servlet-api"
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'tools.jackson.core:jackson-databind'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"

View File

@ -21,10 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
@ -33,6 +29,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -191,8 +189,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponse(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@ -203,8 +200,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOidc(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@ -216,8 +212,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOAuth2(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@ -384,8 +379,8 @@ public class JwtDecodersTests {
// @formatter:on
}
public String buildResponseWithMissingJwksUri() throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
public String buildResponseWithMissingJwksUri() {
JsonMapper mapper = new JsonMapper();
Map<String, Object> response = mapper.readValue(DEFAULT_RESPONSE_TEMPLATE,
new TypeReference<Map<String, Object>>() {
});

Some files were not shown because too many files have changed in this diff Show More