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
@@ -46,6 +46,7 @@ dependencies {
optional 'org.springframework:spring-tx'
optional 'org.springframework:spring-webflux'
optional 'org.springframework:spring-webmvc'
optional 'tools.jackson.core:jackson-databind'
optional libs.webauthn4j.core
provided 'jakarta.servlet:jakarta.servlet-api'
@@ -0,0 +1,65 @@
/*
* 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.web.jackson;
import jakarta.servlet.http.Cookie;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.node.MissingNode;
import tools.jackson.databind.node.NullNode;
/**
* Jackson deserializer for {@link Cookie}. This is needed because in most cases we don't
* set {@link Cookie#getDomain()} property. So when jackson deserialize that json
* {@link Cookie#setDomain(String)} throws {@link NullPointerException}. This is
* registered with {@link CookieMixin} but you can also use it with your own mixin.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see CookieMixin
*/
class CookieDeserializer extends ValueDeserializer<Cookie> {
@Override
public Cookie deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException {
JsonNode jsonNode = ctxt.readTree(jp);
Cookie cookie = new Cookie(readJsonNode(jsonNode, "name").stringValue(),
readJsonNode(jsonNode, "value").stringValue());
JsonNode domainNode = readJsonNode(jsonNode, "domain");
cookie.setDomain((domainNode.isMissingNode()) ? null : domainNode.stringValue());
cookie.setMaxAge(readJsonNode(jsonNode, "maxAge").asInt(-1));
cookie.setSecure(readJsonNode(jsonNode, "secure").asBoolean());
JsonNode pathNode = readJsonNode(jsonNode, "path");
cookie.setPath((pathNode.isMissingNode()) ? null : pathNode.stringValue());
JsonNode attributes = readJsonNode(jsonNode, "attributes");
cookie.setHttpOnly(readJsonNode(attributes, "HttpOnly") != null);
return cookie;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return hasNonNullField(jsonNode, field) ? jsonNode.get(field) : MissingNode.getInstance();
}
private boolean hasNonNullField(JsonNode jsonNode, String field) {
return jsonNode.has(field) && !(jsonNode.get(field) instanceof NullNode);
}
}
@@ -0,0 +1,37 @@
/*
* 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.web.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* Mixin class to serialize/deserialize {@link jakarta.servlet.http.Cookie}
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebServletJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = CookieDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class CookieMixin {
}
@@ -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.web.jackson;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson mixin class to serialize/deserialize
* {@link org.springframework.security.web.csrf.DefaultCsrfToken} serialization support.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class DefaultCsrfTokenMixin {
/**
* JsonCreator constructor needed by Jackson to create
* {@link org.springframework.security.web.csrf.DefaultCsrfToken} object.
* @param headerName the name of the header
* @param parameterName the parameter name
* @param token the CSRF token value
*/
@JsonCreator
DefaultCsrfTokenMixin(@JsonProperty("headerName") String headerName,
@JsonProperty("parameterName") String parameterName, @JsonProperty("token") String token) {
}
}
@@ -0,0 +1,45 @@
/*
* 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.web.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
/**
* Jackson mixin class to serialize/deserialize {@link DefaultSavedRequest}. This mixin
* use {@link DefaultSavedRequest.Builder} to deserialized json.In order to use this mixin
* class you also need to register {@link CookieMixin}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebServletJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(builder = DefaultSavedRequest.Builder.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class DefaultSavedRequestMixin {
@JsonInclude(JsonInclude.Include.NON_NULL)
String matchingRequestParameterName;
}
@@ -0,0 +1,79 @@
/*
* 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.web.jackson;
import java.util.List;
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.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
/**
* Custom deserializer for {@link PreAuthenticatedAuthenticationToken}. 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
* @since 7.0
* @see PreAuthenticatedAuthenticationTokenMixin
*/
class PreAuthenticatedAuthenticationTokenDeserializer extends ValueDeserializer<PreAuthenticatedAuthenticationToken> {
private static final TypeReference<List<GrantedAuthority>> GRANTED_AUTHORITY_LIST = new TypeReference<>() {
};
/**
* This method construct {@link PreAuthenticatedAuthenticationToken} object from
* serialized json.
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws tools.jackson.core.JacksonException if an error during JSON processing
* occurs
*/
@Override
public PreAuthenticatedAuthenticationToken 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 = (!principalNode.isObject()) ? principalNode.stringValue()
: ctxt.readTreeAsValue(principalNode, Object.class);
Object credentials = readJsonNode(jsonNode, "credentials").stringValue();
JsonNode authoritiesNode = readJsonNode(jsonNode, "authorities");
List<GrantedAuthority> authorities = ctxt.readTreeAsValue(authoritiesNode,
ctxt.getTypeFactory().constructType(GRANTED_AUTHORITY_LIST));
PreAuthenticatedAuthenticationToken token = (!authenticated)
? new PreAuthenticatedAuthenticationToken(principal, credentials)
: new PreAuthenticatedAuthenticationToken(principal, credentials, authorities);
token.setDetails(readJsonNode(jsonNode, "details"));
return token;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
@@ -0,0 +1,43 @@
/*
* 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.web.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.jackson.SecurityJacksonModules;
/**
* This mixin class is used to serialize / deserialize
* {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}.
* This class register a custom deserializer
* {@link PreAuthenticatedAuthenticationTokenDeserializer}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebJacksonModule
* @see SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = PreAuthenticatedAuthenticationTokenDeserializer.class)
abstract class PreAuthenticatedAuthenticationTokenMixin {
}
@@ -0,0 +1,45 @@
/*
* 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.web.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 to serialize/deserialize
* {@link org.springframework.security.web.savedrequest.SavedCookie} serialization
* support.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebServletJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class SavedCookieMixin {
@JsonCreator
SavedCookieMixin(@JsonProperty("name") String name, @JsonProperty("value") String value,
@JsonProperty("domain") String domain, @JsonProperty("maxAge") int maxAge,
@JsonProperty("path") String path, @JsonProperty("secure") boolean secure) {
}
}
@@ -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.web.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.core.Authentication;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
/**
* Jackson mixin class to serialize/deserialize {@link SwitchUserGrantedAuthority}.
*
* @author Sebastien Deleuze
* @author Markus Heiden
* @since 7.0
* @see WebJacksonModule
* @see WebServletJacksonModule
* @see org.springframework.security.jackson.SecurityJacksonModules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class SwitchUserGrantedAuthorityMixIn {
@JsonCreator
SwitchUserGrantedAuthorityMixIn(@JsonProperty("role") String role, @JsonProperty("source") Authentication source) {
}
}
@@ -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.web.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 to serialize/deserialize
* {@link org.springframework.security.web.authentication.WebAuthenticationDetails}.
*
* @author Sebastien Deleuze
* @author Jitendra Singh
* @since 7.0
* @see WebServletJacksonModule
* @see org.springframework.security.jackson.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)
class WebAuthenticationDetailsMixin {
@JsonCreator
WebAuthenticationDetailsMixin(@JsonProperty("remoteAddress") String remoteAddress,
@JsonProperty("sessionId") String sessionId) {
}
}
@@ -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.web.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.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.SavedCookie;
/**
* Jackson module for spring-security-web. This module register
* {@link DefaultCsrfTokenMixin}, {@link PreAuthenticatedAuthenticationTokenMixin} and
* {@link SwitchUserGrantedAuthorityMixIn}.
*
* <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
*/
@SuppressWarnings("serial")
public class WebJacksonModule extends SecurityJacksonModule {
public WebJacksonModule() {
super(WebJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(DefaultCsrfToken.class)
.allowIfSubType(SavedCookie.class)
.allowIfSubType(DefaultSavedRequest.class)
.allowIfSubType(WebAuthenticationDetails.class)
.allowIfSubType(PreAuthenticatedAuthenticationToken.class)
.allowIfSubType(SwitchUserGrantedAuthority.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
context.setMixIn(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
context.setMixIn(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixIn.class);
}
}
@@ -0,0 +1,73 @@
/*
* 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.web.jackson;
import jakarta.servlet.http.Cookie;
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.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.SavedCookie;
import org.springframework.security.web.server.csrf.DefaultCsrfToken;
/**
* Jackson module for spring-security-web related to servlet. This module registers
* {@link CookieMixin}, {@link SavedCookieMixin}, {@link DefaultSavedRequestMixin},
* {@link WebAuthenticationDetailsMixin}, and {@link SwitchUserGrantedAuthorityMixIn}.
*
* <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 Boris Finkelshteyn
* @since 7.0
* @see SecurityJacksonModules
*/
@SuppressWarnings("serial")
public class WebServletJacksonModule extends SecurityJacksonModule {
public WebServletJacksonModule() {
super(WebServletJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(Cookie.class).allowIfSubType(DefaultCsrfToken.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(Cookie.class, CookieMixin.class);
context.setMixIn(SavedCookie.class, SavedCookieMixin.class);
context.setMixIn(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
context.setMixIn(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
context.setMixIn(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixIn.class);
}
}
@@ -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 web.
*/
package org.springframework.security.web.jackson;
@@ -15,10 +15,7 @@
*/
/**
* Mix-in classes to provide Jackson serialization support.
*
* @author Jitendra Singh
* @since 4.2
* Jackson 2 serialization support for web.
*/
@NullMarked
package org.springframework.security.web.jackson2;
@@ -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.web.server.jackson;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Jackson mixin class to serialize/deserialize
* {@link org.springframework.security.web.server.csrf.DefaultCsrfToken} serialization
* support.
*
* @author Sebastien Deleuze
* @author Boris Finkelshteyn
* @since 7.0
* @see WebServerJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class DefaultCsrfServerTokenMixin {
/**
* JsonCreator constructor needed by Jackson to create
* {@link org.springframework.security.web.server.csrf.DefaultCsrfToken} object.
* @param headerName the name of the header
* @param parameterName the parameter name
* @param token the CSRF token value
*/
@JsonCreator
DefaultCsrfServerTokenMixin(@JsonProperty("headerName") String headerName,
@JsonProperty("parameterName") String parameterName, @JsonProperty("token") String token) {
}
}
@@ -0,0 +1,65 @@
/*
* 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.web.server.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.web.server.csrf.DefaultCsrfToken;
/**
* Jackson module for spring-security-web-flux. This module register
* {@link DefaultCsrfServerTokenMixin}.
*
* <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 Boris Finkelshteyn
* @since 5.1
* @see SecurityJacksonModules
*/
@SuppressWarnings("serial")
public class WebServerJacksonModule extends SecurityJacksonModule {
private static final String NAME = WebServerJacksonModule.class.getName();
private static final Version VERSION = new Version(1, 0, 0, null, null, null);
public WebServerJacksonModule() {
super(NAME, VERSION);
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(DefaultCsrfToken.class, DefaultCsrfServerTokenMixin.class);
}
}
@@ -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 for reactive web server.
*/
@NullMarked
package org.springframework.security.web.server.jackson;
import org.jspecify.annotations.NullMarked;
@@ -15,7 +15,7 @@
*/
/**
* Reactive web jackson2 integration.
* Jackson 2 serialization support for reactive web server.
*/
@NullMarked
package org.springframework.security.web.server.jackson2;
@@ -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.web.jackson;
import org.junit.jupiter.api.BeforeEach;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
/**
* @author Sebastien Deleuze
* @author Jitenra Singh
*/
public abstract class AbstractMixinTests {
protected JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
}
@@ -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.web.jackson;
import jakarta.servlet.http.Cookie;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class CookieMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String COOKIE_JSON = "{" +
" \"@class\": \"jakarta.servlet.http.Cookie\"," +
" \"name\": \"demo\"," +
" \"value\": \"cookie1\"," +
" \"attributes\":{\"@class\":\"java.util.Collections$EmptyMap\"}," +
" \"comment\": null," +
" \"maxAge\": -1," +
" \"path\": null," +
" \"secure\": false," +
" \"version\": 0," +
" \"domain\": null" +
"}";
// @formatter:on
// @formatter:off
private static final String COOKIE_HTTP_ONLY_JSON = "{" +
" \"@class\": \"jakarta.servlet.http.Cookie\"," +
" \"name\": \"demo\"," +
" \"value\": \"cookie1\"," +
" \"attributes\":{\"@class\":\"java.util.Collections$UnmodifiableMap\", \"HttpOnly\": \"\"}," +
" \"comment\": null," +
" \"maxAge\": -1," +
" \"path\": null," +
" \"secure\": false," +
" \"version\": 0," +
" \"domain\": null" +
"}";
// @formatter:on
@Test
public void serializeCookie() throws JacksonException, JSONException {
Cookie cookie = new Cookie("demo", "cookie1");
String actualString = this.mapper.writeValueAsString(cookie);
JSONAssert.assertEquals(COOKIE_JSON, actualString, true);
}
@Test
public void deserializeCookie() {
Cookie cookie = this.mapper.readValue(COOKIE_JSON, Cookie.class);
assertThat(cookie).isNotNull();
assertThat(cookie.getName()).isEqualTo("demo");
assertThat(cookie.getDomain()).isNull();
}
@Test
public void serializeCookieWithHttpOnly() throws JacksonException, JSONException {
Cookie cookie = new Cookie("demo", "cookie1");
cookie.setHttpOnly(true);
String actualString = this.mapper.writeValueAsString(cookie);
JSONAssert.assertEquals(COOKIE_HTTP_ONLY_JSON, actualString, true);
}
@Test
public void deserializeCookieWithHttpOnly() {
Cookie cookie = this.mapper.readValue(COOKIE_HTTP_ONLY_JSON, Cookie.class);
assertThat(cookie).isNotNull();
assertThat(cookie.getName()).isEqualTo("demo");
assertThat(cookie.getDomain()).isNull();
assertThat(cookie.isHttpOnly()).isEqualTo(true);
}
}
@@ -0,0 +1,73 @@
/*
* 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.web.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class DefaultCsrfTokenMixinTests extends AbstractMixinTests {
// @formatter:off
public static final String CSRF_JSON = "{"
+ "\"@class\": \"org.springframework.security.web.csrf.DefaultCsrfToken\", "
+ "\"headerName\": \"csrf-header\", "
+ "\"parameterName\": \"_csrf\", "
+ "\"token\": \"1\""
+ "}";
// @formatter:on
@Test
public void defaultCsrfTokenSerializedTest() throws JacksonException, JSONException {
DefaultCsrfToken token = new DefaultCsrfToken("csrf-header", "_csrf", "1");
String serializedJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CSRF_JSON, serializedJson, true);
}
@Test
public void defaultCsrfTokenDeserializeTest() {
DefaultCsrfToken token = this.mapper.readValue(CSRF_JSON, DefaultCsrfToken.class);
assertThat(token).isNotNull();
assertThat(token.getHeaderName()).isEqualTo("csrf-header");
assertThat(token.getParameterName()).isEqualTo("_csrf");
assertThat(token.getToken()).isEqualTo("1");
}
@Test
public void defaultCsrfTokenDeserializeWithoutClassTest() {
String tokenJson = "{\"headerName\": \"csrf-header\", \"parameterName\": \"_csrf\", \"token\": \"1\"}";
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> this.mapper.readValue(tokenJson, DefaultCsrfToken.class));
}
@Test
public void defaultCsrfTokenDeserializeNullValuesTest() {
String tokenJson = "{\"@class\": \"org.springframework.security.web.csrf.DefaultCsrfToken\", \"headerName\": \"\", \"parameterName\": null, \"token\": \"1\"}";
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> this.mapper.readValue(tokenJson, DefaultCsrfToken.class));
}
}
@@ -0,0 +1,172 @@
/*
* 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.web.jackson;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.SavedCookie;
import org.springframework.security.web.util.UrlUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class DefaultSavedRequestMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String COOKIES_JSON = "[\"java.util.ArrayList\", [{"
+ "\"@class\": \"org.springframework.security.web.savedrequest.SavedCookie\", "
+ "\"name\": \"SESSION\", "
+ "\"value\": \"123456789\", "
+ "\"maxAge\": -1, "
+ "\"path\": null, "
+ "\"secure\":false, "
+ "\"domain\": null"
+ "}]]";
// @formatter:on
// @formatter:off
private static final String REQUEST_JSON = "{" +
"\"@class\": \"org.springframework.security.web.savedrequest.DefaultSavedRequest\", "
+ "\"cookies\": " + COOKIES_JSON + ","
+ "\"locales\": [\"java.util.ArrayList\", [\"en\"]], "
+ "\"headers\": {\"@class\": \"java.util.TreeMap\", \"x-auth-token\": [\"java.util.ArrayList\", [\"12\"]]}, "
+ "\"parameters\": {\"@class\": \"java.util.TreeMap\"},"
+ "\"contextPath\": \"\", "
+ "\"method\": \"\", "
+ "\"pathInfo\": null, "
+ "\"queryString\": null, "
+ "\"requestURI\": \"\", "
+ "\"requestURL\": \"http://localhost\", "
+ "\"scheme\": \"http\", "
+ "\"serverName\": \"localhost\", "
+ "\"servletPath\": \"\", "
+ "\"serverPort\": 80"
+ "}";
// @formatter:on
// @formatter:off
private static final String REQUEST_WITH_MATCHING_REQUEST_PARAM_NAME_JSON = "{" +
"\"@class\": \"org.springframework.security.web.savedrequest.DefaultSavedRequest\", "
+ "\"cookies\": " + COOKIES_JSON + ","
+ "\"locales\": [\"java.util.ArrayList\", [\"en\"]], "
+ "\"headers\": {\"@class\": \"java.util.TreeMap\", \"x-auth-token\": [\"java.util.ArrayList\", [\"12\"]]}, "
+ "\"parameters\": {\"@class\": \"java.util.TreeMap\"},"
+ "\"contextPath\": \"\", "
+ "\"method\": \"\", "
+ "\"pathInfo\": null, "
+ "\"queryString\": null, "
+ "\"requestURI\": \"\", "
+ "\"requestURL\": \"http://localhost\", "
+ "\"scheme\": \"http\", "
+ "\"serverName\": \"localhost\", "
+ "\"servletPath\": \"\", "
+ "\"serverPort\": 80, "
+ "\"matchingRequestParameterName\": \"success\""
+ "}";
// @formatter:on
@Test
public void matchRequestBuildWithConstructorAndBuilder() {
DefaultSavedRequest request = new DefaultSavedRequest.Builder()
.setCookies(Collections.singletonList(new SavedCookie(new Cookie("SESSION", "123456789"))))
.setHeaders(Collections.singletonMap("x-auth-token", Collections.singletonList("12")))
.setScheme("http")
.setRequestURL("http://localhost")
.setServerName("localhost")
.setRequestURI("")
.setLocales(Collections.singletonList(new Locale("en")))
.setContextPath("")
.setMethod("")
.setServletPath("")
.build();
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.setCookies(new Cookie("SESSION", "123456789"));
mockRequest.addHeader("x-auth-token", "12");
String currentUrl = UrlUtils.buildFullRequestUrl(mockRequest);
assertThat(request.getRedirectUrl().equals(currentUrl)).isTrue();
}
@Test
public void serializeDefaultRequestBuildWithConstructorTest() throws IOException, JSONException {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("x-auth-token", "12");
// Spring 5 MockHttpServletRequest automatically adds a header when the cookies
// are set. To get consistency we override the request.
HttpServletRequest requestToWrite = new HttpServletRequestWrapper(request) {
@Override
public Cookie[] getCookies() {
return new Cookie[] { new Cookie("SESSION", "123456789") };
}
};
String actualString = this.mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(new DefaultSavedRequest(requestToWrite));
JSONAssert.assertEquals(REQUEST_JSON, actualString, true);
}
@Test
public void serializeDefaultRequestBuildWithBuilderTest() throws IOException, JSONException {
DefaultSavedRequest request = new DefaultSavedRequest.Builder()
.setCookies(Collections.singletonList(new SavedCookie(new Cookie("SESSION", "123456789"))))
.setHeaders(Collections.singletonMap("x-auth-token", Collections.singletonList("12")))
.setScheme("http")
.setRequestURL("http://localhost")
.setServerName("localhost")
.setRequestURI("")
.setLocales(Collections.singletonList(new Locale("en")))
.setContextPath("")
.setMethod("")
.setServletPath("")
.build();
String actualString = this.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(request);
JSONAssert.assertEquals(REQUEST_JSON, actualString, true);
}
@Test
public void deserializeDefaultSavedRequest() {
DefaultSavedRequest request = (DefaultSavedRequest) this.mapper.readValue(REQUEST_JSON, Object.class);
assertThat(request).isNotNull();
assertThat(request.getCookies()).hasSize(1);
assertThat(request.getLocales()).hasSize(1).contains(new Locale("en"));
assertThat(request.getHeaderNames()).hasSize(1).contains("x-auth-token");
assertThat(request.getHeaderValues("x-auth-token")).hasSize(1).contains("12");
}
@Test
public void deserializeWhenMatchingRequestParameterNameThenRedirectUrlContainsParam() {
DefaultSavedRequest request = (DefaultSavedRequest) this.mapper
.readValue(REQUEST_WITH_MATCHING_REQUEST_PARAM_NAME_JSON, Object.class);
assertThat(request.getRedirectUrl()).isEqualTo("http://localhost?success");
}
@Test
public void deserializeWhenNullMatchingRequestParameterNameThenRedirectUrlDoesNotContainParam() {
DefaultSavedRequest request = (DefaultSavedRequest) this.mapper.readValue(REQUEST_JSON, Object.class);
assertThat(request.getRedirectUrl()).isEqualTo("http://localhost");
}
}
@@ -0,0 +1,70 @@
/*
* 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.web.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 org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson2.SimpleGrantedAuthorityMixinTests;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 4.2
*/
public class PreAuthenticatedAuthenticationTokenMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String PREAUTH_JSON = "{"
+ "\"@class\": \"org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken\","
+ "\"principal\": \"principal\", "
+ "\"credentials\": \"credentials\", "
+ "\"authenticated\": true, "
+ "\"details\": null, "
+ "\"authorities\": " + SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+ "}";
// @formatter:on
PreAuthenticatedAuthenticationToken expected;
@BeforeEach
public void setupExpected() {
this.expected = new PreAuthenticatedAuthenticationToken("principal", "credentials",
AuthorityUtils.createAuthorityList("ROLE_USER"));
}
@Test
public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws JacksonException, JSONException {
String serializedJson = this.mapper.writeValueAsString(this.expected);
JSONAssert.assertEquals(PREAUTH_JSON, serializedJson, true);
}
@Test
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenMixinTest() {
PreAuthenticatedAuthenticationToken deserialized = this.mapper.readValue(PREAUTH_JSON,
PreAuthenticatedAuthenticationToken.class);
assertThat(deserialized).isNotNull();
assertThat(deserialized.isAuthenticated()).isTrue();
assertThat(deserialized.getAuthorities()).isEqualTo(this.expected.getAuthorities());
}
}
@@ -0,0 +1,98 @@
/*
* 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.web.jackson;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.Cookie;
import org.json.JSONException;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import org.springframework.security.web.savedrequest.SavedCookie;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
*/
public class SavedCookieMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String COOKIE_JSON = "{"
+ "\"@class\": \"org.springframework.security.web.savedrequest.SavedCookie\", "
+ "\"name\": \"SESSION\", "
+ "\"value\": \"123456789\", "
+ "\"maxAge\": -1, "
+ "\"path\": null, "
+ "\"secure\":false, "
+ "\"domain\": null"
+ "}";
// @formatter:on
// @formatter:off
private static final String COOKIES_JSON = "[\"java.util.ArrayList\", ["
+ COOKIE_JSON
+ "]]";
// @formatter:on
@Test
public void serializeWithDefaultConfigurationTest() throws JacksonException, JSONException {
SavedCookie savedCookie = new SavedCookie(new Cookie("SESSION", "123456789"));
String actualJson = this.mapper.writeValueAsString(savedCookie);
JSONAssert.assertEquals(COOKIE_JSON, actualJson, true);
}
@Test
@Disabled("No supported by Jackson 3 as ObjectMapper/JsonMapper is immutable")
public void serializeWithOverrideConfigurationTest() throws JacksonException, JSONException {
SavedCookie savedCookie = new SavedCookie(new Cookie("SESSION", "123456789"));
// this.mapper.setVisibility(PropertyAccessor.FIELD,
// JsonAutoDetect.Visibility.PUBLIC_ONLY)
// .setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
String actualJson = this.mapper.writeValueAsString(savedCookie);
JSONAssert.assertEquals(COOKIE_JSON, actualJson, true);
}
@Test
public void serializeSavedCookieWithList() throws JacksonException, JSONException {
List<SavedCookie> savedCookies = new ArrayList<>();
savedCookies.add(new SavedCookie(new Cookie("SESSION", "123456789")));
String actualJson = this.mapper.writeValueAsString(savedCookies);
JSONAssert.assertEquals(COOKIES_JSON, actualJson, true);
}
@Test
@SuppressWarnings("unchecked")
public void deserializeSavedCookieWithList() {
List<SavedCookie> savedCookies = (List<SavedCookie>) this.mapper.readValue(COOKIES_JSON, Object.class);
assertThat(savedCookies).isNotNull().hasSize(1);
assertThat(savedCookies.get(0).getName()).isEqualTo("SESSION");
assertThat(savedCookies.get(0).getValue()).isEqualTo("123456789");
}
@Test
public void deserializeSavedCookieJsonTest() {
SavedCookie savedCookie = (SavedCookie) this.mapper.readValue(COOKIE_JSON, Object.class);
assertThat(savedCookie).isNotNull();
assertThat(savedCookie.getName()).isEqualTo("SESSION");
assertThat(savedCookie.getValue()).isEqualTo("123456789");
assertThat(savedCookie.isSecure()).isEqualTo(false);
}
}
@@ -0,0 +1,82 @@
/*
* 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.web.jackson;
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.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.jackson.SimpleGrantedAuthorityMixinTests;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Markus Heiden
* @since 6.3
*/
public class SwitchUserGrantedAuthorityMixInTests {
// language=JSON
private static final String SWITCH_JSON = """
{
"@class": "org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority",
"role": "switched",
"source": {
"@class": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"principal": "principal",
"credentials": "credentials",
"authenticated": true,
"details": null,
"authorities": %s
}
}
""".formatted(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON);
private Authentication source;
private JsonMapper mapper;
@BeforeEach
public void setUp() {
this.source = new UsernamePasswordAuthenticationToken("principal", "credentials",
AuthorityUtils.createAuthorityList("ROLE_USER"));
ClassLoader classLoader = SwitchUserGrantedAuthorityMixInTests.class.getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(classLoader)).build();
}
@Test
public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws Exception {
SwitchUserGrantedAuthority expected = new SwitchUserGrantedAuthority("switched", this.source);
String serializedJson = this.mapper.writeValueAsString(expected);
JSONAssert.assertEquals(SWITCH_JSON, serializedJson, true);
}
@Test
public void deserializeWhenSourceIsUsernamePasswordAuthenticationTokenThenSuccess() {
SwitchUserGrantedAuthority deserialized = this.mapper.readValue(SWITCH_JSON, SwitchUserGrantedAuthority.class);
assertThat(deserialized).isNotNull();
assertThat(deserialized.getAuthority()).isEqualTo("switched");
assertThat(deserialized.getSource()).isEqualTo(this.source);
}
}
@@ -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.web.jackson;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class WebAuthenticationDetailsMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String AUTHENTICATION_DETAILS_JSON = "{"
+ "\"@class\": \"org.springframework.security.web.authentication.WebAuthenticationDetails\","
+ "\"sessionId\": \"1\", "
+ "\"remoteAddress\": "
+ "\"/localhost\""
+ "}";
// @formatter:on
@Test
public void buildWebAuthenticationDetailsUsingDifferentConstructors() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRemoteAddr("localhost");
request.setSession(new MockHttpSession(null, "1"));
WebAuthenticationDetails details = new WebAuthenticationDetails(request);
WebAuthenticationDetails authenticationDetails = this.mapper.readValue(AUTHENTICATION_DETAILS_JSON,
WebAuthenticationDetails.class);
assertThat(details.equals(authenticationDetails));
}
@Test
public void webAuthenticationDetailsSerializeTest() throws JacksonException, JSONException {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRemoteAddr("/localhost");
request.setSession(new MockHttpSession(null, "1"));
WebAuthenticationDetails details = new WebAuthenticationDetails(request);
String actualJson = this.mapper.writeValueAsString(details);
JSONAssert.assertEquals(AUTHENTICATION_DETAILS_JSON, actualJson, true);
}
@Test
public void webAuthenticationDetailsJackson2SerializeTest() throws JacksonException, JSONException {
WebAuthenticationDetails details = new WebAuthenticationDetails("/localhost", "1");
String actualJson = this.mapper.writeValueAsString(details);
JSONAssert.assertEquals(AUTHENTICATION_DETAILS_JSON, actualJson, true);
}
@Test
public void webAuthenticationDetailsDeserializeTest() {
WebAuthenticationDetails details = this.mapper.readValue(AUTHENTICATION_DETAILS_JSON,
WebAuthenticationDetails.class);
assertThat(details).isNotNull();
assertThat(details.getRemoteAddress()).isEqualTo("/localhost");
assertThat(details.getSessionId()).isEqualTo("1");
}
}
@@ -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.web.server.jackson;
import java.io.IOException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import org.springframework.security.web.jackson.AbstractMixinTests;
import org.springframework.security.web.server.csrf.DefaultCsrfToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Sebastien Deleuze
* @author Boris Finkelshteyn
*/
public class DefaultCsrfServerTokenMixinTests extends AbstractMixinTests {
// @formatter:off
private static final String CSRF_JSON = "{"
+ "\"@class\": \"org.springframework.security.web.server.csrf.DefaultCsrfToken\", "
+ "\"headerName\": \"csrf-header\", "
+ "\"parameterName\": \"_csrf\", "
+ "\"token\": \"1\""
+ "}";
// @formatter:on
@Test
public void defaultCsrfTokenSerializedTest() throws JacksonException, JSONException {
DefaultCsrfToken token = new DefaultCsrfToken("csrf-header", "_csrf", "1");
String serializedJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CSRF_JSON, serializedJson, true);
}
@Test
public void defaultCsrfTokenDeserializeTest() throws IOException {
DefaultCsrfToken token = this.mapper.readValue(CSRF_JSON, DefaultCsrfToken.class);
assertThat(token).isNotNull();
assertThat(token.getHeaderName()).isEqualTo("csrf-header");
assertThat(token.getParameterName()).isEqualTo("_csrf");
assertThat(token.getToken()).isEqualTo("1");
}
@Test
public void defaultCsrfTokenDeserializeWithoutClassTest() throws IOException {
String tokenJson = "{\"headerName\": \"csrf-header\", \"parameterName\": \"_csrf\", \"token\": \"1\"}";
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> this.mapper.readValue(tokenJson, DefaultCsrfToken.class));
}
@Test
public void defaultCsrfTokenDeserializeNullValuesTest() throws IOException {
String tokenJson = "{\"@class\": \"org.springframework.security.web.server.csrf.DefaultCsrfToken\", \"headerName\": \"\", \"parameterName\": null, \"token\": \"1\"}";
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> this.mapper.readValue(tokenJson, DefaultCsrfToken.class));
}
}