1
0
mirror of synced 2026-05-22 21:33:16 +00:00

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
+1
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'
@@ -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) {
}
}
@@ -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) {
}
}
@@ -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);
}
}
@@ -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) {
}
}
@@ -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) {
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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) {
}
}
@@ -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();
}
}
@@ -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 {
}
@@ -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();
}
}
@@ -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 {
}
@@ -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;
@@ -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");
@@ -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;
@@ -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");
@@ -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));
}
}
@@ -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);
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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"));
}
}
@@ -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";
}
}
}
@@ -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));
}
}
@@ -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"));
}
}
@@ -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);
}
}
@@ -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;
}
}
}