Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 608b1484e4 | |||
| 5fb2875f47 | |||
| 27ae318992 | |||
| 73840663b9 | |||
| 5662e17370 | |||
| 63f28a7e1f | |||
| f988272fff | |||
| 532d0bef14 | |||
| 571bd60d82 | |||
| 6471a32d66 | |||
| c1e9e10bf0 | |||
| fed6df5167 | |||
| 20ae9dc6bc | |||
| 03eadb846c | |||
| 0928a60cd2 | |||
| 322634ca6a | |||
| 5213cc44fc | |||
| 8fa2fc0e1e | |||
| 4feeb0f843 | |||
| ea88671f4c | |||
| 9dde69746f | |||
| 884cf0d62e | |||
| aaf738f7ac | |||
| ccd39a23c9 | |||
| 793820acfa | |||
| b6ed037c39 | |||
| 5da0cbea4b | |||
| e6b4d461e7 | |||
| 4daf089e46 | |||
| 6501e97ece | |||
| 3a84894bf4 | |||
| 90855aa128 | |||
| 9f7e92d6f2 | |||
| 727f0e27d6 | |||
| f548aaf5c5 | |||
| 743817fc15 | |||
| fb701e4615 | |||
| 1c112005fa | |||
| e0a71eb00e | |||
| 69d28dc35b | |||
| 42ddaba870 | |||
| da46ba2619 | |||
| a406f5fe2d | |||
| dcb4e47cd5 | |||
| 82f87cf2b6 | |||
| 0a2f55d485 | |||
| 9b61533db2 | |||
| eb43830260 | |||
| b55c28cf25 | |||
| 0927bed66a | |||
| 9ed446e6f5 | |||
| d5e6da5aba | |||
| 4d2bd30c75 | |||
| 5acad99852 | |||
| e616688f56 | |||
| 56a23d9ddc | |||
| dc5aed9b5f | |||
| 9c7b34a48b | |||
| d5d7fd414d | |||
| 491a3e8f68 | |||
| 43d20ea91f | |||
| 24241d0384 | |||
| cb8c2b090c | |||
| e94de4d0e3 | |||
| cb994aad6c | |||
| 6f6ee0c060 | |||
| 9cecc2cf09 | |||
| f19c9c8625 | |||
| 95abf61c88 | |||
| 22cbb13f7d | |||
| fc8b6b5863 | |||
| 8b89e31e3d | |||
| 67b15be917 | |||
| 217a29e6ba | |||
| b2d6380633 | |||
| 9dc27bee03 | |||
| a181733365 | |||
| 51e8f8f1c6 | |||
| d309f1887e | |||
| 5e851e0b26 | |||
| 803936cfbe | |||
| 50568da1e5 | |||
| 8f8a25533a | |||
| 137f8fd670 | |||
| 702a177e25 | |||
| 48854c3ac9 | |||
| 65a14d6c6d | |||
| 916a687b29 | |||
| 762fcbb516 | |||
| fc795a81d4 | |||
| 4bc319883b | |||
| cb7a6292b7 | |||
| bbf6a4e786 | |||
| ba2619cb8a | |||
| 43c53c3b78 | |||
| b1e16cd147 | |||
| 9961e6d56c | |||
| cbad2ff5ca | |||
| 63c8b0faa3 | |||
| a435175723 | |||
| 4b810a8971 | |||
| 0d261e9c32 | |||
| c5e141ad07 | |||
| ba42b9c4cc | |||
| b1a50a25b6 | |||
| 4281f6b00b | |||
| 0fcef6dca2 | |||
| 2e7cdd7b14 | |||
| cefc0cddec | |||
| af1de950ae | |||
| 7f29585df4 | |||
| 2eb5da3764 | |||
| f5d33457dc | |||
| 938a5a7c77 | |||
| f03213383e | |||
| fc2b1f9923 | |||
| dee33b5337 | |||
| 9f936015ff | |||
| 79dfbe14c2 | |||
| b75f2582c4 | |||
| 67c3ceb611 | |||
| 95644fb73c | |||
| 21ff7688cc | |||
| 4102007119 | |||
| e535e61c8b | |||
| fbf7bb3be1 | |||
| 979ac7c336 | |||
| 90a1c2c15d | |||
| 978459bd1d | |||
| 73690a928b | |||
| 7cc9d2849e |
@@ -0,0 +1,27 @@
|
||||
name: Finalize Release
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
inputs:
|
||||
version:
|
||||
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
|
||||
required: true
|
||||
|
||||
env:
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
|
||||
with:
|
||||
should-perform-release: true
|
||||
project-version: ${{ inputs.version }}
|
||||
milestone-repo-url: https://repo1.maven.org/maven2
|
||||
release-repo-url: https://repo1.maven.org/maven2
|
||||
artifact-path: org/springframework/security/spring-security-core
|
||||
slack-announcing-id: spring-security-announcing
|
||||
secrets: inherit
|
||||
@@ -25,6 +25,7 @@ atlassian-ide-plugin.xml
|
||||
s101plugin.state
|
||||
.attach_pid*
|
||||
.~lock.*#
|
||||
.kotlin/
|
||||
|
||||
!.idea/checkstyle-idea.xml
|
||||
!.idea/externalDependencies.xml
|
||||
|
||||
@@ -71,7 +71,7 @@ import org.springframework.util.StringUtils;
|
||||
* <tt>AclEntryVoter</tt>:
|
||||
* <ul>
|
||||
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
|
||||
* <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
|
||||
* <code>VOTE_ACL_BANK_ACCOUNT_READ</code>, require permission
|
||||
* <code>BasePermission.READ</code></li>
|
||||
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
|
||||
* <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
|
||||
|
||||
@@ -65,10 +65,11 @@ import org.springframework.util.Assert;
|
||||
* NB: This implementation does attempt to provide reasonably optimised lookups - within
|
||||
* the constraints of a normalised database and standard ANSI SQL features. If you are
|
||||
* willing to sacrifice either of these constraints (e.g. use a particular database
|
||||
* feature such as hierarchical queries or materalized views, or reduce normalisation) you
|
||||
* are likely to achieve better performance. In such situations you will need to provide
|
||||
* your own custom <code>LookupStrategy</code>. This class does not support subclassing,
|
||||
* as it is likely to change in future releases and therefore subclassing is unsupported.
|
||||
* feature such as hierarchical queries or materialized views, or reduce normalisation)
|
||||
* you are likely to achieve better performance. In such situations you will need to
|
||||
* provide your own custom <code>LookupStrategy</code>. This class does not support
|
||||
* subclassing, as it is likely to change in future releases and therefore subclassing is
|
||||
* unsupported.
|
||||
* <p>
|
||||
* There are two SQL queries executed, one in the <tt>lookupPrimaryKeys</tt> method and
|
||||
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencies {
|
||||
api 'org.springframework:spring-web'
|
||||
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
optional 'tools.jackson.core:jackson-databind'
|
||||
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.authentication.AttributePrincipal;
|
||||
|
||||
/**
|
||||
* Helps in jackson deserialization of class
|
||||
* {@link org.apereo.cas.client.validation.AssertionImpl}, which is used with
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Jitendra Singh
|
||||
* @since 7.0
|
||||
* @see CasJacksonModule
|
||||
* @see org.springframework.security.jackson.SecurityJacksonModules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
class AssertionImplMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize
|
||||
* {@link org.apereo.cas.client.validation.AssertionImpl}
|
||||
* @param principal the Principal to associate with the Assertion.
|
||||
* @param validFromDate when the assertion is valid from.
|
||||
* @param validUntilDate when the assertion is valid to.
|
||||
* @param authenticationDate when the assertion is authenticated.
|
||||
* @param attributes the key/value pairs for this attribute.
|
||||
*/
|
||||
@JsonCreator
|
||||
AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal,
|
||||
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
|
||||
@JsonProperty("authenticationDate") Date authenticationDate,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes) {
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.proxy.ProxyRetriever;
|
||||
|
||||
/**
|
||||
* Helps in deserialize
|
||||
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Jitendra Singh
|
||||
* @since 7.0
|
||||
* @see CasJacksonModule
|
||||
* @see org.springframework.security.jackson.SecurityJacksonModules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
class AttributePrincipalImplMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize
|
||||
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl}
|
||||
* @param name the unique identifier for the principal.
|
||||
* @param attributes the key/value pairs for this principal.
|
||||
* @param proxyGrantingTicket the ticket associated with this principal.
|
||||
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS
|
||||
* server.
|
||||
*/
|
||||
@JsonCreator
|
||||
AttributePrincipalImplMixin(@JsonProperty("name") String name,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes,
|
||||
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
|
||||
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* Mixin class which helps in deserialize {@link CasAuthenticationToken} using jackson.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Jitendra Singh
|
||||
* @since 7.0
|
||||
* @see CasJacksonModule
|
||||
* @see org.springframework.security.jackson.SecurityJacksonModules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
class CasAuthenticationTokenMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor helps in deserialize {@link CasAuthenticationToken}
|
||||
* @param keyHash hashCode of provided key to identify if this object made by a given
|
||||
* {@link CasAuthenticationProvider}
|
||||
* @param principal typically the UserDetails object (cannot be <code>null</code>)
|
||||
* @param credentials the service/proxy ticket ID from CAS (cannot be
|
||||
* <code>null</code>)
|
||||
* @param authorities the authorities granted to the user (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param userDetails the user details (from the
|
||||
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
|
||||
* be <code>null</code>)
|
||||
* @param assertion the assertion returned from the CAS servers. It contains the
|
||||
* principal and how to obtain a proxy ticket for the user.
|
||||
*/
|
||||
@JsonCreator
|
||||
CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
|
||||
@JsonProperty("credentials") Object credentials,
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson;
|
||||
|
||||
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
import tools.jackson.core.Version;
|
||||
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.jackson.SecurityJacksonModule;
|
||||
import org.springframework.security.jackson.SecurityJacksonModules;
|
||||
|
||||
/**
|
||||
* Jackson module for spring-security-cas. This module register
|
||||
* {@link AssertionImplMixin}, {@link AttributePrincipalImplMixin} and
|
||||
* {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then it'll
|
||||
* enable it because typing info is needed to properly serialize/deserialize objects. In
|
||||
* order to use this module just add this module into your JsonMapper configuration.
|
||||
*
|
||||
* <p>
|
||||
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
|
||||
* to enable properly automatic inclusion of type information with related validation.
|
||||
*
|
||||
* <pre>
|
||||
* ClassLoader loader = getClass().getClassLoader();
|
||||
* JsonMapper mapper = JsonMapper.builder()
|
||||
* .addModules(SecurityJacksonModules.getModules(loader))
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Jitendra Singh
|
||||
* @since 7.0
|
||||
* @see SecurityJacksonModules
|
||||
*/
|
||||
public class CasJacksonModule extends SecurityJacksonModule {
|
||||
|
||||
public CasJacksonModule() {
|
||||
super(CasJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
|
||||
builder.allowIfSubType(AssertionImpl.class)
|
||||
.allowIfSubType(AttributePrincipalImpl.class)
|
||||
.allowIfSubType(CasAuthenticationToken.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
context.setMixIn(AssertionImpl.class, AssertionImplMixin.class);
|
||||
context.setMixIn(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class);
|
||||
context.setMixIn(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jackson 3+ serialization support for CAS.
|
||||
*/
|
||||
package org.springframework.security.cas.jackson;
|
||||
@@ -33,6 +33,7 @@ import org.apereo.cas.client.authentication.AttributePrincipal;
|
||||
* this class we need to register with
|
||||
* {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information will be stored
|
||||
* in @class property.
|
||||
*
|
||||
* <p>
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
@@ -43,7 +44,11 @@ import org.apereo.cas.client.authentication.AttributePrincipal;
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.cas.jackson.AssertionImplMixin} based on Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
|
||||
+6
@@ -30,6 +30,7 @@ import org.apereo.cas.client.proxy.ProxyRetriever;
|
||||
* {@link org.apereo.cas.client.authentication.AttributePrincipalImpl} which is used with
|
||||
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type
|
||||
* information will be stored in property named @class.
|
||||
*
|
||||
* <p>
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
@@ -40,7 +41,12 @@ import org.apereo.cas.client.proxy.ProxyRetriever;
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.cas.jackson.AttributePrincipalImplMixin} based on
|
||||
* Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
|
||||
+5
-1
@@ -40,7 +40,6 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CasJackson2Module());
|
||||
@@ -50,7 +49,12 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
* @since 4.2
|
||||
* @see CasJackson2Module
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.cas.jackson.CasAuthenticationTokenMixin} based on
|
||||
* Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
|
||||
@@ -37,11 +37,14 @@ import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
|
||||
* of all security modules on the classpath.</b>
|
||||
*
|
||||
* @author Jitendra Singh.
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@link org.springframework.security.cas.jackson.CasJacksonModule} based on Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings({ "serial", "removal" })
|
||||
public class CasJackson2Module extends SimpleModule {
|
||||
|
||||
public CasJackson2Module() {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jackson support for CAS.
|
||||
* Jackson 2 support for CAS.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.cas.jackson2;
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.cas.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apereo.cas.client.authentication.AttributePrincipalImpl;
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.jackson.SecurityJacksonModules;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
*/
|
||||
public class CasAuthenticationTokenMixinTests {
|
||||
|
||||
private static final String KEY = "casKey";
|
||||
|
||||
private static final String PASSWORD = "\"1234\"";
|
||||
|
||||
private static final Date START_DATE = new Date();
|
||||
|
||||
private static final Date END_DATE = new Date();
|
||||
|
||||
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
|
||||
|
||||
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON
|
||||
+ "]]";
|
||||
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", ["
|
||||
+ AUTHORITY_JSON + "]]";
|
||||
|
||||
// @formatter:off
|
||||
public static final String USER_JSON = "{"
|
||||
+ "\"@class\": \"org.springframework.security.core.userdetails.User\", "
|
||||
+ "\"username\": \"admin\","
|
||||
+ " \"password\": " + PASSWORD + ", "
|
||||
+ "\"accountNonExpired\": true, "
|
||||
+ "\"accountNonLocked\": true, "
|
||||
+ "\"credentialsNonExpired\": true, "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"authorities\": " + AUTHORITIES_SET_JSON
|
||||
+ "}";
|
||||
// @formatter:on
|
||||
private static final String CAS_TOKEN_JSON = "{"
|
||||
+ "\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", "
|
||||
+ "\"keyHash\": " + KEY.hashCode() + "," + "\"principal\": " + USER_JSON + ", " + "\"credentials\": "
|
||||
+ PASSWORD + ", " + "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + "," + "\"userDetails\": " + USER_JSON
|
||||
+ "," + "\"authenticated\": true, " + "\"details\": null," + "\"assertion\": {"
|
||||
+ "\"@class\": \"org.apereo.cas.client.validation.AssertionImpl\", " + "\"principal\": {"
|
||||
+ "\"@class\": \"org.apereo.cas.client.authentication.AttributePrincipalImpl\", "
|
||||
+ "\"name\": \"assertName\", " + "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
|
||||
+ "\"proxyGrantingTicket\": null, " + "\"proxyRetriever\": null" + "}, "
|
||||
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
|
||||
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"},"
|
||||
+ "\"context\": {\"@class\":\"java.util.HashMap\"}" + "}" + "}";
|
||||
|
||||
private static final String CAS_TOKEN_CLEARED_JSON = CAS_TOKEN_JSON.replaceFirst(PASSWORD, "null");
|
||||
|
||||
protected JsonMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeCasAuthenticationTest() throws JSONException {
|
||||
CasAuthenticationToken token = createCasAuthenticationToken();
|
||||
String actualJson = this.mapper.writeValueAsString(token);
|
||||
JSONAssert.assertEquals(CAS_TOKEN_JSON, actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeCasAuthenticationTestAfterEraseCredentialInvoked() throws JSONException {
|
||||
CasAuthenticationToken token = createCasAuthenticationToken();
|
||||
token.eraseCredentials();
|
||||
String actualJson = this.mapper.writeValueAsString(token);
|
||||
JSONAssert.assertEquals(CAS_TOKEN_CLEARED_JSON, actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeCasAuthenticationTestAfterEraseCredentialInvoked() {
|
||||
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_CLEARED_JSON, CasAuthenticationToken.class);
|
||||
assertThat(((UserDetails) token.getPrincipal()).getPassword()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeCasAuthenticationTest() throws IOException {
|
||||
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_JSON, CasAuthenticationToken.class);
|
||||
assertThat(token).isNotNull();
|
||||
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
|
||||
assertThat(((User) token.getPrincipal()).getUsername()).isEqualTo("admin");
|
||||
assertThat(((User) token.getPrincipal()).getPassword()).isEqualTo("1234");
|
||||
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
|
||||
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
|
||||
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
|
||||
assertThat(token.getUserDetails().getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||
.containsOnly("ROLE_USER");
|
||||
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
|
||||
assertThat(token.getAssertion().getPrincipal().getName()).isEqualTo("assertName");
|
||||
assertThat(token.getAssertion().getAttributes()).hasSize(0);
|
||||
}
|
||||
|
||||
private CasAuthenticationToken createCasAuthenticationToken() {
|
||||
User principal = new User("admin", "1234", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
|
||||
Collection<? extends GrantedAuthority> authorities = Collections
|
||||
.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), START_DATE, END_DATE,
|
||||
START_DATE, Collections.<String, Object>emptyMap());
|
||||
return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities,
|
||||
new User("admin", "1234", authorities), assertion);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class CasAuthenticationTokenMixinTests {
|
||||
|
||||
private static final String KEY = "casKey";
|
||||
|
||||
+5
-5
@@ -27,14 +27,14 @@ import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
|
||||
/**
|
||||
* Uses {@link EnableGlobalMultiFactorAuthentication} to configure a
|
||||
* Uses {@link EnableMultiFactorAuthentication} to configure a
|
||||
* {@link DefaultAuthorizationManagerFactory}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see EnableGlobalMultiFactorAuthentication
|
||||
* @see EnableMultiFactorAuthentication
|
||||
*/
|
||||
class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
|
||||
class AuthorizationManagerFactoryConfiguration implements ImportAware {
|
||||
|
||||
private String[] authorities;
|
||||
|
||||
@@ -49,9 +49,9 @@ class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> multiFactorAuthenticationAttrs = importMetadata
|
||||
.getAnnotationAttributes(EnableGlobalMultiFactorAuthentication.class.getName());
|
||||
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
|
||||
|
||||
this.authorities = (String[]) multiFactorAuthenticationAttrs.get("authorities");
|
||||
this.authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-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.config.annotation.authorization;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class EnableMfaFiltersConfiguration {
|
||||
|
||||
@Bean
|
||||
BeanPostProcessor mfaBeanPostProcessor() {
|
||||
return new EnableMfaFiltersPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BeanPostProcessor} that enables MFA on authentication filters.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
private static class EnableMfaFiltersPostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof AbstractAuthenticationProcessingFilter filter) {
|
||||
filter.setMfaEnabled(true);
|
||||
}
|
||||
if (bean instanceof AuthenticationFilter filter) {
|
||||
filter.setMfaEnabled(true);
|
||||
}
|
||||
if (bean instanceof AbstractPreAuthenticatedProcessingFilter filter) {
|
||||
filter.setMfaEnabled(true);
|
||||
}
|
||||
if (bean instanceof BasicAuthenticationFilter filter) {
|
||||
filter.setMfaEnabled(true);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+13
-8
@@ -26,9 +26,12 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
|
||||
/**
|
||||
* Exposes a {@link DefaultAuthorizationManagerFactory} as a Bean with the
|
||||
* {@link #authorities()} specified as additional required authorities. The configuration
|
||||
* will be picked up by both
|
||||
* Enables Multi-Factor Authentication (MFA) support within Spring Security.
|
||||
*
|
||||
* When {@link #authorities()} is specified creates a
|
||||
* {@link DefaultAuthorizationManagerFactory} as a Bean with the {@link #authorities()}
|
||||
* specified as additional required authorities. The configuration will be picked up by
|
||||
* both
|
||||
* {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
|
||||
* and
|
||||
* {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}.
|
||||
@@ -36,7 +39,7 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
|
||||
* <pre>
|
||||
|
||||
* @Configuration
|
||||
* @EnableGlobalMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
|
||||
* @EnableMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
|
||||
* public class MyConfiguration {
|
||||
* // ...
|
||||
* }
|
||||
@@ -51,14 +54,16 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(GlobalMultiFactorAuthenticationConfiguration.class)
|
||||
public @interface EnableGlobalMultiFactorAuthentication {
|
||||
@Import(MultiFactorAuthenticationSelector.class)
|
||||
public @interface EnableMultiFactorAuthentication {
|
||||
|
||||
/**
|
||||
* The additional authorities that are required.
|
||||
* @return the additional authorities that are required (e.g. {
|
||||
* GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
|
||||
* @see org.springframework.security.core.GrantedAuthorities
|
||||
* FactorGrantedAuthority.FACTOR_OTT, FactorGrantedAuthority.FACTOR_PASSWORD }). Can
|
||||
* be null or an empty array if no additional authorities are required (if
|
||||
* authorization rules are not globally requiring MFA).
|
||||
* @see org.springframework.security.core.authority.FactorGrantedAuthority
|
||||
*/
|
||||
String[] authorities();
|
||||
|
||||
+50
@@ -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.config.annotation.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
|
||||
/**
|
||||
* Uses {@link EnableMultiFactorAuthentication} to configure a
|
||||
* {@link DefaultAuthorizationManagerFactory}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see EnableMultiFactorAuthentication
|
||||
*/
|
||||
class MultiFactorAuthenticationSelector implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata metadata) {
|
||||
Map<String, Object> multiFactorAuthenticationAttrs = metadata
|
||||
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
|
||||
String[] authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
|
||||
List<String> imports = new ArrayList<>(2);
|
||||
if (authorities.length > 0) {
|
||||
imports.add(AuthorizationManagerFactoryConfiguration.class.getName());
|
||||
}
|
||||
imports.add(EnableMfaFiltersConfiguration.class.getName());
|
||||
return imports.toArray(new String[imports.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -2052,7 +2052,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
* http
|
||||
* // ...
|
||||
* .webAuthn((webAuthn) -> webAuthn
|
||||
* .rpName("Spring Security Relying Party")
|
||||
* .rpId("example.com")
|
||||
* .allowedOrigins("https://example.com")
|
||||
* );
|
||||
|
||||
+5
-3
@@ -177,6 +177,7 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
|
||||
webAuthnAuthnFilter.setAuthenticationManager(
|
||||
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
|
||||
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
|
||||
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
|
||||
rpOperations);
|
||||
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
|
||||
@@ -256,9 +257,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
|
||||
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
|
||||
WebAuthnRelyingPartyOperations.class);
|
||||
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
|
||||
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
|
||||
this.allowedOrigins));
|
||||
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
|
||||
return webauthnOperationsBean
|
||||
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
|
||||
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
@@ -482,6 +482,27 @@ public final class OAuth2AuthorizationServerConfigurer
|
||||
});
|
||||
}
|
||||
|
||||
OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer(
|
||||
OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
|
||||
if (pushedAuthorizationRequestEndpointConfigurer != null) {
|
||||
OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataEndpointConfigurer = getConfigurer(
|
||||
OAuth2AuthorizationServerMetadataEndpointConfigurer.class);
|
||||
|
||||
authorizationServerMetadataEndpointConfigurer.addDefaultAuthorizationServerMetadataCustomizer((builder) -> {
|
||||
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
|
||||
String issuer = authorizationServerContext.getIssuer();
|
||||
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext
|
||||
.getAuthorizationServerSettings();
|
||||
|
||||
String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer)
|
||||
.path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint);
|
||||
});
|
||||
}
|
||||
|
||||
this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity));
|
||||
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
|
||||
+22
@@ -171,6 +171,28 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
|
||||
});
|
||||
}
|
||||
|
||||
OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = httpSecurity
|
||||
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.getConfigurer(OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
|
||||
if (pushedAuthorizationRequestEndpointConfigurer != null) {
|
||||
OidcProviderConfigurationEndpointConfigurer providerConfigurationEndpointConfigurer = getConfigurer(
|
||||
OidcProviderConfigurationEndpointConfigurer.class);
|
||||
|
||||
providerConfigurationEndpointConfigurer.addDefaultProviderConfigurationCustomizer((builder) -> {
|
||||
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
|
||||
String issuer = authorizationServerContext.getIssuer();
|
||||
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext
|
||||
.getAuthorizationServerSettings();
|
||||
|
||||
String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer)
|
||||
.path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint);
|
||||
});
|
||||
}
|
||||
|
||||
this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity));
|
||||
}
|
||||
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.config.web.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* {@link GenericHttpMessageConverter} implementation that delegates to a
|
||||
* {@link SmartHttpMessageConverter}.
|
||||
*
|
||||
* @param <T> the converted object type
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
*/
|
||||
final class GenericHttpMessageConverterAdapter<T> implements GenericHttpMessageConverter<T> {
|
||||
|
||||
private final SmartHttpMessageConverter<T> smartConverter;
|
||||
|
||||
GenericHttpMessageConverterAdapter(SmartHttpMessageConverter<T> smartConverter) {
|
||||
this.smartConverter = smartConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
|
||||
return this.smartConverter.canRead(ResolvableType.forType(type), mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
return this.smartConverter.read(ResolvableType.forType(type), inputMessage, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
|
||||
return this.smartConverter.canWrite(ResolvableType.forType(type), clazz, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
this.smartConverter.write(t, ResolvableType.forType(type), contentType, outputMessage, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
|
||||
return this.smartConverter.canRead(ResolvableType.forClass(clazz), mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
|
||||
return this.smartConverter.canWrite(clazz, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return this.smartConverter.getSupportedMediaTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
return this.smartConverter.read(clazz, inputMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
this.smartConverter.write(t, contentType, outputMessage);
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -19,6 +19,7 @@ package org.springframework.security.config.web.server;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -32,6 +33,8 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
final class HttpMessageConverters {
|
||||
|
||||
private static final boolean jacksonPresent;
|
||||
|
||||
private static final boolean jackson2Present;
|
||||
|
||||
private static final boolean gsonPresent;
|
||||
@@ -40,6 +43,7 @@ final class HttpMessageConverters {
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
|
||||
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
|
||||
@@ -49,7 +53,11 @@ final class HttpMessageConverters {
|
||||
private HttpMessageConverters() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
|
||||
if (jacksonPresent) {
|
||||
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
|
||||
}
|
||||
if (jackson2Present) {
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import jakarta.servlet.http.HttpSession
|
||||
* @since 5.3
|
||||
* @property clearAuthentication whether the [SecurityContextLogoutHandler] should clear
|
||||
* the [Authentication] at the time of logout.
|
||||
* @property clearAuthentication whether to invalidate the [HttpSession] at the time of logout.
|
||||
* @property invalidateHttpSession whether to invalidate the [HttpSession] at the time of logout.
|
||||
* @property logoutUrl the URL that triggers log out to occur.
|
||||
* @property logoutRequestMatcher the [RequestMatcher] that triggers log out to occur.
|
||||
* @property logoutSuccessUrl the URL to redirect to after logout has occurred.
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.config.annotation.authorization;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link EnableMultiFactorAuthentication}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration
|
||||
@WithMockUser(authorities = FactorGrantedAuthority.PASSWORD_AUTHORITY)
|
||||
public class EnableMultiFactorAuthenticationFiltersSetTests {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager manager;
|
||||
|
||||
private TestingAuthenticationToken newAuthn = new TestingAuthenticationToken("user", "password", "ROLE_USER",
|
||||
FactorGrantedAuthority.OTT_AUTHORITY);
|
||||
|
||||
@Test
|
||||
void preAuthenticationFilter(@Autowired AbstractAuthenticationProcessingFilter filter) throws Exception {
|
||||
assertMfaEnabled(filter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationFilter(@Autowired AuthenticationFilter filter) throws Exception {
|
||||
assertMfaEnabled(filter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preAuthnFilter(@Autowired AbstractPreAuthenticatedProcessingFilter filter) throws Exception {
|
||||
assertMfaEnabled(filter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicAuthnFilter(@Autowired BasicAuthenticationFilter filter) throws Exception {
|
||||
assertMfaEnabled(filter);
|
||||
}
|
||||
|
||||
private void assertMfaEnabled(Filter filter) throws Exception {
|
||||
given(this.manager.authenticate(any())).willReturn(this.newAuthn);
|
||||
MockHttpServletRequest request = MockMvcRequestBuilders.get("/")
|
||||
.headers((headers) -> headers.setBasicAuth("u", "p"))
|
||||
.buildRequest(new MockServletContext());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
filter.doFilter(request, response, chain);
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
assertThat(authentication).isNotNull();
|
||||
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||
.containsExactlyInAnyOrder(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY,
|
||||
"ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableMultiFactorAuthentication(
|
||||
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
AuthenticationManager authenticationManager() {
|
||||
return mock(AuthenticationManager.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
static AbstractAuthenticationProcessingFilter authnProcessingFilter(
|
||||
AuthenticationManager authenticationManager) {
|
||||
AbstractAuthenticationProcessingFilter result = new AbstractAuthenticationProcessingFilter(
|
||||
AnyRequestMatcher.INSTANCE, authenticationManager) {
|
||||
};
|
||||
result.setAuthenticationConverter(new BasicAuthenticationConverter());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Bean
|
||||
static AuthenticationFilter authenticationFilter(AuthenticationManager authenticationManager) {
|
||||
return new AuthenticationFilter(authenticationManager, new BasicAuthenticationConverter());
|
||||
}
|
||||
|
||||
@Bean
|
||||
static AbstractPreAuthenticatedProcessingFilter preAuthenticatedProcessingFilter(
|
||||
AuthenticationManager authenticationManager) {
|
||||
AbstractPreAuthenticatedProcessingFilter result = new AbstractPreAuthenticatedProcessingFilter() {
|
||||
@Override
|
||||
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
return "password";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||
return "user";
|
||||
}
|
||||
};
|
||||
result.setRequiresAuthenticationRequestMatcher(AnyRequestMatcher.INSTANCE);
|
||||
result.setAuthenticationManager(authenticationManager);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Bean
|
||||
static BasicAuthenticationFilter basicAuthenticationFilter(AuthenticationManager authenticationManager) {
|
||||
return new BasicAuthenticationFilter(authenticationManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+57
-3
@@ -16,6 +16,13 @@
|
||||
|
||||
package org.springframework.security.config.annotation.authorization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -25,10 +32,18 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -37,18 +52,22 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link EnableGlobalMultiFactorAuthentication}.
|
||||
* Tests for {@link EnableMultiFactorAuthentication}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration
|
||||
public class EnableGlobalMultiFactorAuthenticationTests {
|
||||
public class EnableMultiFactorAuthenticationTests {
|
||||
|
||||
private static final String ATTR_NAME = "org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors$SecurityContextRequestPostProcessorSupport$TestSecurityContextRepository.REPO";
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
@@ -56,6 +75,14 @@ public class EnableGlobalMultiFactorAuthenticationTests {
|
||||
@Autowired
|
||||
Service service;
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { "ROLE_USER", FactorGrantedAuthority.OTT_AUTHORITY })
|
||||
public void formLoginWhenAuthenticatedThenMergedAuthorities() throws Exception {
|
||||
this.mvc.perform(formLogin())
|
||||
.andExpect(authenticated().withAuthorities("ROLE_USER", FactorGrantedAuthority.OTT_AUTHORITY,
|
||||
FactorGrantedAuthority.PASSWORD_AUTHORITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY })
|
||||
void webWhenAuthorized() throws Exception {
|
||||
@@ -84,7 +111,7 @@ public class EnableGlobalMultiFactorAuthenticationTests {
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
@EnableGlobalMultiFactorAuthentication(
|
||||
@EnableMultiFactorAuthentication(
|
||||
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
|
||||
static class Config {
|
||||
|
||||
@@ -98,6 +125,33 @@ public class EnableGlobalMultiFactorAuthenticationTests {
|
||||
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<HttpSecurity> captureAuthn() {
|
||||
return (http) -> http.addFilterAfter(new Filter() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
|
||||
FilterChain filterChain) throws IOException, ServletException {
|
||||
try {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
finally {
|
||||
servletRequest.setAttribute(ATTR_NAME, SecurityContextHolder.getContext());
|
||||
}
|
||||
}
|
||||
}, SecurityContextHolderFilter.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("deprecation")
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class OkController {
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ import static org.springframework.security.web.servlet.util.matcher.PathPatternR
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -175,7 +175,7 @@ public class NamespaceHttpTests {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrlPattern("**/entry-point"));
|
||||
.andExpect(redirectedUrl("/entry-point"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ public class DefaultLoginPageConfigurerTests {
|
||||
@Test
|
||||
public void getWhenFormLoginEnabledThenRedirectsToLoginPage() throws Exception {
|
||||
this.spring.register(DefaultLoginPageConfig.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-2
@@ -214,8 +214,7 @@ public class ExceptionHandlingConfigurerTests {
|
||||
@Test
|
||||
public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception {
|
||||
this.spring.register(DefaultHttpConfig.class).autowire();
|
||||
this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-7
@@ -181,7 +181,7 @@ public class FormLoginConfigurerTests {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/private"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ public class FormLoginConfigurerTests {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/private"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ public class FormLoginConfigurerTests {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/login?error"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -403,7 +403,8 @@ public class FormLoginConfigurerTests {
|
||||
UserDetails user = PasswordEncodedUser.user();
|
||||
this.mockMvc.perform(get("/profile").with(user(user)))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
|
||||
.andExpect(redirectedUrl(
|
||||
"/login?factor.type=password&factor.type=ott&factor.reason=missing&factor.reason=missing"));
|
||||
this.mockMvc
|
||||
.perform(post("/ott/generate").param("username", "rod")
|
||||
.with(user(user))
|
||||
@@ -421,13 +422,13 @@ public class FormLoginConfigurerTests {
|
||||
.build();
|
||||
this.mockMvc.perform(get("/profile").with(user(user)))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
|
||||
.andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing"));
|
||||
user = PasswordEncodedUser.withUserDetails(user)
|
||||
.authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY)
|
||||
.build();
|
||||
this.mockMvc.perform(get("/profile").with(user(user)))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"));
|
||||
.andExpect(redirectedUrl("/login?factor.type=ott&factor.reason=missing"));
|
||||
user = PasswordEncodedUser.withUserDetails(user)
|
||||
.authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY,
|
||||
FactorGrantedAuthority.OTT_AUTHORITY)
|
||||
@@ -444,7 +445,7 @@ public class FormLoginConfigurerTests {
|
||||
this.mockMvc.perform(get("/login")).andExpect(status().isOk());
|
||||
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
|
||||
.andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing"));
|
||||
this.mockMvc
|
||||
.perform(post("/login").param("username", "rod")
|
||||
.param("password", "password")
|
||||
|
||||
+3
-3
@@ -66,7 +66,7 @@ public class NamespaceHttpFormLoginTests {
|
||||
@Test
|
||||
public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception {
|
||||
this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
|
||||
this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error"));
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder loginRequest = post("/login")
|
||||
@@ -80,7 +80,7 @@ public class NamespaceHttpFormLoginTests {
|
||||
@Test
|
||||
public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception {
|
||||
this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/authentication/login"));
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login"));
|
||||
this.mvc.perform(post("/authentication/login/process").with(csrf()))
|
||||
.andExpect(redirectedUrl("/authentication/login?failed"));
|
||||
// @formatter:off
|
||||
@@ -95,7 +95,7 @@ public class NamespaceHttpFormLoginTests {
|
||||
@Test
|
||||
public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception {
|
||||
this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("/login"));
|
||||
this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure"));
|
||||
verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class));
|
||||
// @formatter:off
|
||||
|
||||
+2
-2
@@ -104,7 +104,7 @@ public class NamespaceRememberMeTests {
|
||||
.with(csrf())
|
||||
.cookie(rememberMe);
|
||||
this.mvc.perform(authenticationClassRequest)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class NamespaceRememberMeTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(somewhereRequest)
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin());
|
||||
Cookie withKey = this.mvc.perform(loginWithRememberme)
|
||||
.andReturn()
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ public class RememberMeConfigurerTests {
|
||||
.with(csrf())
|
||||
.cookie(expiredRememberMeCookie);
|
||||
// @formatter:on
|
||||
this.mvc.perform(expiredRequest).andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+11
-11
@@ -90,7 +90,7 @@ public class RequestCacheConfigurerTests {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -104,7 +104,7 @@ public class RequestCacheConfigurerTests {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -120,7 +120,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -140,7 +140,7 @@ public class RequestCacheConfigurerTests {
|
||||
.header("X-Requested-With", "XMLHttpRequest");
|
||||
MockHttpSession session = (MockHttpSession) this.mvc
|
||||
.perform(xRequestedWith)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -157,7 +157,7 @@ public class RequestCacheConfigurerTests {
|
||||
MediaType.TEXT_EVENT_STREAM);
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -174,7 +174,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages").header("Upgrade", "websocket");
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -191,7 +191,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL);
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -205,7 +205,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -220,7 +220,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages")
|
||||
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -235,7 +235,7 @@ public class RequestCacheConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/messages")
|
||||
.header("X-Requested-With", "com.android");
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(request)
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
@@ -315,7 +315,7 @@ public class RequestCacheConfigurerTests {
|
||||
.autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
|
||||
+89
-2
@@ -19,10 +19,13 @@ package org.springframework.security.config.annotation.web.configurers;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
@@ -42,6 +45,7 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
|
||||
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
|
||||
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -52,6 +56,8 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
@@ -88,6 +94,14 @@ public class WebAuthnConfigurerTests {
|
||||
.andExpect(content().string(containsString("body {")));
|
||||
}
|
||||
|
||||
// gh-18128
|
||||
@Test
|
||||
public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception {
|
||||
this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire();
|
||||
PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class);
|
||||
assertThat(postProcess.webauthnFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
|
||||
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
|
||||
@@ -127,6 +141,42 @@ public class WebAuthnConfigurerTests {
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
||||
String response = this.mvc
|
||||
.perform(post("/webauthn/register/options").with(csrf())
|
||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode parsedResponse = mapper.readTree(response);
|
||||
|
||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
|
||||
String response = this.mvc
|
||||
.perform(post("/webauthn/register/options").with(csrf())
|
||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode parsedResponse = mapper.readTree(response);
|
||||
|
||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
|
||||
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
|
||||
@@ -289,6 +339,26 @@ public class WebAuthnConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class PostProcessorConfiguration {
|
||||
|
||||
WebAuthnAuthenticationFilter webauthnFilter;
|
||||
|
||||
@Bean
|
||||
BeanPostProcessor beanPostProcessor() {
|
||||
return new BeanPostProcessor() {
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
if (bean instanceof WebAuthnAuthenticationFilter filter) {
|
||||
PostProcessorConfiguration.this.webauthnFilter = filter;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class DefaultWebauthnConfiguration {
|
||||
@@ -304,8 +374,7 @@ public class WebAuthnConfigurerTests {
|
||||
http
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((authn) -> authn
|
||||
.rpId("spring.io")
|
||||
.rpName("spring")
|
||||
.rpId("example.com")
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
@@ -313,6 +382,24 @@ public class WebAuthnConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class CustomRpNameWebauthnConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class NoFormLoginAndDefaultRegistrationPageConfiguration {
|
||||
|
||||
+7
-7
@@ -447,7 +447,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google");
|
||||
}
|
||||
|
||||
// gh-6802
|
||||
@@ -457,7 +457,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/login");
|
||||
}
|
||||
|
||||
// gh-5347
|
||||
@@ -469,7 +469,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
this.request = get(requestUri).build();
|
||||
this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString());
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/login");
|
||||
}
|
||||
|
||||
// gh-5347
|
||||
@@ -479,7 +479,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/login");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/login");
|
||||
}
|
||||
|
||||
// gh-6812
|
||||
@@ -524,7 +524,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -533,7 +533,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/custom-login");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -542,7 +542,7 @@ public class OAuth2LoginConfigurerTests {
|
||||
String requestUri = "/";
|
||||
this.request = get(requestUri).build();
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
|
||||
assertThat(this.response.getRedirectedUrl()).matches("/custom-login");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-25
@@ -34,7 +34,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
@@ -44,7 +43,6 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@@ -141,11 +139,7 @@ public class JwkSetTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -163,24 +157,6 @@ public class JwkSetTests {
|
||||
return jwkSource;
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+115
-26
@@ -116,7 +116,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
|
||||
@@ -1071,6 +1070,94 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
// gh-2182
|
||||
@Test
|
||||
public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
|
||||
scopes.clear();
|
||||
scopes.add("message.read");
|
||||
scopes.add("message.write");
|
||||
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
MvcResult mvcResult = this.mvc
|
||||
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.request_uri").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
|
||||
|
||||
String consentPage = this.mvc
|
||||
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
assertThat(consentPage).contains("Consent required");
|
||||
assertThat(consentPage).contains(scopeCheckbox("message.read"));
|
||||
assertThat(consentPage).contains(scopeCheckbox("message.write"));
|
||||
}
|
||||
|
||||
// gh-2182
|
||||
@Test
|
||||
public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class)
|
||||
.autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
|
||||
scopes.clear();
|
||||
scopes.add("message.read");
|
||||
scopes.add("message.write");
|
||||
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
MvcResult mvcResult = this.mvc
|
||||
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.request_uri").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
|
||||
|
||||
mvcResult = this.mvc
|
||||
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+");
|
||||
|
||||
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
|
||||
MultiValueMap<String, String> redirectQueryParams = uriComponents.getQueryParams();
|
||||
|
||||
assertThat(uriComponents.getPath()).isEqualTo(consentPage);
|
||||
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write");
|
||||
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID))
|
||||
.isEqualTo(registeredClient.getClientId());
|
||||
|
||||
String state = extractParameterFromRedirectUri(redirectedUrl, "state");
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE);
|
||||
assertThat(authorization).isNotNull();
|
||||
}
|
||||
|
||||
private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) {
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
|
||||
@@ -1125,8 +1212,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
|
||||
String clientId = registeredClient.getClientId();
|
||||
String clientSecret = registeredClient.getClientSecret();
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
|
||||
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
|
||||
String credentialsString = clientId + ":" + clientSecret;
|
||||
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
|
||||
@@ -1151,11 +1238,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -1208,24 +1291,6 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@@ -1496,4 +1561,28 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage
|
||||
extends AuthorizationServerConfiguration {
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2AuthorizationServer((authorizationServer) ->
|
||||
authorizationServer
|
||||
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
|
||||
.authorizationEndpoint((authorizationEndpoint) ->
|
||||
authorizationEndpoint.consentPage(consentPage))
|
||||
)
|
||||
.authorizeHttpRequests((authorize) ->
|
||||
authorize.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
@@ -185,6 +185,17 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
.andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAuthorizationServerMetadataRequestAndPushedAuthorizationRequestEnabledThenMetadataResponseIncludesPushedAuthorizationRequestEndpoint()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("$.pushed_authorization_request_endpoint")
|
||||
.value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint())));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfiguration {
|
||||
@@ -301,4 +312,26 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled
|
||||
extends AuthorizationServerConfiguration {
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2AuthorizationServer((authorizationServer) ->
|
||||
authorizationServer
|
||||
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
|
||||
)
|
||||
.authorizeHttpRequests((authorize) ->
|
||||
authorize.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-25
@@ -52,7 +52,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
@@ -93,7 +92,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
@@ -533,11 +531,7 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -569,24 +563,6 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+1
-25
@@ -56,7 +56,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
@@ -98,7 +97,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
@@ -467,11 +465,7 @@ public class OAuth2RefreshTokenGrantTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -512,24 +506,6 @@ public class OAuth2RefreshTokenGrantTests {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+1
-25
@@ -51,7 +51,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
@@ -87,7 +86,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
@@ -507,11 +505,7 @@ public class OAuth2TokenIntrospectionTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -549,24 +543,6 @@ public class OAuth2TokenIntrospectionTests {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+1
-25
@@ -43,7 +43,6 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
@@ -71,7 +70,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
@@ -316,11 +314,7 @@ public class OAuth2TokenRevocationTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -347,24 +341,6 @@ public class OAuth2TokenRevocationTests {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+32
@@ -159,6 +159,18 @@ public class OidcProviderConfigurationTests {
|
||||
.andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenConfigurationRequestAndPushedAuthorizationRequestEnabledThenConfigurationResponseIncludesPushedAuthorizationRequestEndpoint()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER))
|
||||
.andExpect(jsonPath("$.pushed_authorization_request_endpoint")
|
||||
.value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint())));
|
||||
}
|
||||
|
||||
private ResultMatcher[] defaultConfigurationMatchers(String issuer) {
|
||||
// @formatter:off
|
||||
return new ResultMatcher[] {
|
||||
@@ -357,6 +369,26 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled
|
||||
extends AuthorizationServerConfiguration {
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2AuthorizationServer((authorizationServer) ->
|
||||
authorizationServer
|
||||
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
|
||||
.oidc(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration {
|
||||
|
||||
+1
-25
@@ -52,7 +52,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -90,7 +89,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
@@ -631,11 +629,7 @@ public class OidcTests {
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations,
|
||||
registeredClientRepository);
|
||||
authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
|
||||
authorizationService.setAuthorizationParametersMapper(new ParametersMapper());
|
||||
return authorizationService;
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -691,24 +685,6 @@ public class OidcTests {
|
||||
return sessionRegistry;
|
||||
}
|
||||
|
||||
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
|
||||
|
||||
RowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
super(registeredClientRepository);
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
|
||||
|
||||
ParametersMapper() {
|
||||
super();
|
||||
getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+1
-1
@@ -1212,7 +1212,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
||||
MvcResult result = this.mvc.perform(get("/authenticated")
|
||||
.header("Accept", "text/html"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
assertThat(result.getRequest().getSession(false)).isNotNull();
|
||||
|
||||
+1
-3
@@ -191,9 +191,7 @@ public class OneTimeTokenLoginConfigurerTests {
|
||||
@Test
|
||||
void oneTimeTokenWhenLoginPageConfiguredThenRedirects() throws Exception {
|
||||
this.spring.register(OneTimeTokenLoginPageConfig.class).autowire();
|
||||
this.mvc.perform(get("/login"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/custom-login"));
|
||||
this.mvc.perform(get("/login")).andExpect(status().isFound()).andExpect(redirectedUrl("/custom-login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -356,7 +356,7 @@ public class Saml2LoginConfigurerTests {
|
||||
MockHttpServletRequestBuilder request = get("/custom/auth/sso");
|
||||
this.mvc.perform(request)
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/custom/auth/sso?entityId=registration-id"));
|
||||
.andExpect(redirectedUrl("/custom/auth/sso?entityId=registration-id"));
|
||||
request.queryParam("entityId", registration.getRegistrationId());
|
||||
MvcResult result = this.mvc.perform(request).andExpect(status().isFound()).andReturn();
|
||||
String redirectedUrl = result.getResponse().getRedirectedUrl();
|
||||
@@ -407,10 +407,10 @@ public class Saml2LoginConfigurerTests {
|
||||
this.spring.register(Saml2LoginConfig.class).autowire();
|
||||
this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
this.mvc.perform(get("/").accept(MediaType.TEXT_HTML))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(header().string("Location", startsWith("http://localhost/saml2/authenticate")));
|
||||
.andExpect(header().string("Location", startsWith("/saml2/authenticate")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -379,7 +379,7 @@ public class CsrfConfigTests {
|
||||
this.spring.configLocations(this.xml("CsrfEnabled")).autowire();
|
||||
// simulates a request that has no authentication (e.g. session time-out)
|
||||
MvcResult result = this.mvc.perform(post("/authenticated").with(csrf()))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn();
|
||||
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
|
||||
// if the request cache is consulted, then it will redirect back to /some-url,
|
||||
@@ -400,9 +400,7 @@ public class CsrfConfigTests {
|
||||
throws Exception {
|
||||
this.spring.configLocations(this.xml("CsrfEnabled")).autowire();
|
||||
// simulates a request that has no authentication (e.g. session time-out)
|
||||
MvcResult result = this.mvc.perform(get("/authenticated"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn();
|
||||
MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn();
|
||||
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
|
||||
// if the request cache is consulted, then it will redirect back to /some-url,
|
||||
// which we do want
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ public class FormLoginConfigTests {
|
||||
this.spring.configLocations(this.xml("WithRequestMatcher")).autowire();
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class FormLoginConfigTests {
|
||||
this.mvc.perform(invalidPassword)
|
||||
.andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure"));
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTests.URL + "/login"));
|
||||
.andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public class HttpConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class HttpConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class HttpConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
verify(authorizationManager).authorize(any(), any());
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class HttpConfigTests {
|
||||
proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> {
|
||||
});
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login");
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/login");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -612,7 +612,7 @@ public class MiscHttpConfigTests {
|
||||
proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> {
|
||||
});
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login");
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/login");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -841,7 +841,7 @@ public class MiscHttpConfigTests {
|
||||
this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected"))
|
||||
.andExpect(redirectedUrl("https://localhost:9443/login"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession(false);
|
||||
|
||||
+15
-6
@@ -60,7 +60,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
@@ -133,6 +135,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
@Autowired(required = false)
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oidcUserService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
|
||||
|
||||
@@ -175,7 +180,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/oauth2/authorization/google-login"));
|
||||
.andExpect(redirectedUrl("/oauth2/authorization/google-login"));
|
||||
// @formatter:on
|
||||
verify(this.requestCache).saveRequest(any(), any());
|
||||
}
|
||||
@@ -188,7 +193,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -200,7 +205,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -286,6 +291,8 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt);
|
||||
DefaultOidcUser oidcUser = TestOidcUsers.create();
|
||||
given(this.oidcUserService.loadUser(any())).willReturn(oidcUser);
|
||||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||
params.add("code", "code123");
|
||||
params.add("state", authorizationRequest.getState());
|
||||
@@ -339,6 +346,8 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt);
|
||||
DefaultOidcUser oidcUser = TestOidcUsers.create();
|
||||
given(this.oidcUserService.loadUser(any())).willReturn(oidcUser);
|
||||
given(this.userAuthoritiesMapper.mapAuthorities(any()))
|
||||
.willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OIDC_USER"));
|
||||
// @formatter:off
|
||||
@@ -414,7 +423,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -424,7 +433,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/custom-login"));
|
||||
.andExpect(redirectedUrl("/custom-login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -436,7 +445,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ public class PlaceHolderAndELConfigTests {
|
||||
// login-page setting
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/secured"))
|
||||
.andExpect(redirectedUrl("http://localhost/loginPage"));
|
||||
.andExpect(redirectedUrl("/loginPage"));
|
||||
// login-processing-url setting
|
||||
// default-target-url setting
|
||||
this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password"))
|
||||
@@ -98,7 +98,7 @@ public class PlaceHolderAndELConfigTests {
|
||||
// login-page setting
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/secured"))
|
||||
.andExpect(redirectedUrl("http://localhost/loginPage"));
|
||||
.andExpect(redirectedUrl("/loginPage"));
|
||||
// login-processing-url setting
|
||||
// default-target-url setting
|
||||
this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password"))
|
||||
|
||||
+2
-2
@@ -161,7 +161,7 @@ public class Saml2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/one"));
|
||||
.andExpect(redirectedUrl("/saml2/authenticate/one"));
|
||||
// @formatter:on
|
||||
verify(this.requestCache).saveRequest(any(), any());
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public class Saml2LoginBeanDefinitionParserTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
+5
-7
@@ -78,7 +78,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/authenticate"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/authenticate"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -137,10 +137,10 @@ public class SecurityContextHolderAwareRequestConfigTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/authenticate"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
.andExpect(redirectedUrl("/login"));
|
||||
this.mvc.perform(get("/v2/authenticate"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/login2"));
|
||||
.andExpect(redirectedUrl("/login2"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -177,9 +177,7 @@ public class SecurityContextHolderAwareRequestConfigTests {
|
||||
@Test
|
||||
public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception {
|
||||
this.spring.configLocations(this.xml("Logout")).autowire();
|
||||
this.mvc.perform(get("/authenticate"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/signin"));
|
||||
this.mvc.perform(get("/authenticate")).andExpect(status().isFound()).andExpect(redirectedUrl("/signin"));
|
||||
// @formatter:off
|
||||
MvcResult result = this.mvc.perform(get("/good-login"))
|
||||
.andReturn();
|
||||
|
||||
+3
-3
@@ -218,7 +218,7 @@ class ExceptionHandlingDslTests {
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/custom-login")
|
||||
redirectedUrl("/custom-login")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,13 +247,13 @@ class ExceptionHandlingDslTests {
|
||||
this.mockMvc.get("/secured1")
|
||||
.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/custom-login1")
|
||||
redirectedUrl("/custom-login1")
|
||||
}
|
||||
|
||||
this.mockMvc.get("/secured2")
|
||||
.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/custom-login2")
|
||||
redirectedUrl("/custom-login2")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -144,7 +144,7 @@ class FormLoginDslTests {
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/login")
|
||||
redirectedUrl("/login")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ class FormLoginDslTests {
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/log-in")
|
||||
redirectedUrl("/log-in")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -148,7 +148,7 @@ internal class RememberMeDslTests {
|
||||
cookie(expiredRememberMeCookie)
|
||||
}.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/login")
|
||||
redirectedUrl("/login")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ internal class RememberMeDslTests {
|
||||
cookie(withoutKeyRememberMeCookie)
|
||||
}.andExpect {
|
||||
status { isFound() }
|
||||
redirectedUrl("http://localhost/login")
|
||||
redirectedUrl("/login")
|
||||
}
|
||||
val keyMvcResult = mockMvc.post("/login") {
|
||||
loginRememberMeRequest()
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ class Saml2DslTests {
|
||||
val request = MockMvcRequestBuilders.get("/custom/auth/sso")
|
||||
this.mockMvc.perform(request)
|
||||
.andExpect(MockMvcResultMatchers.status().isFound())
|
||||
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/custom/auth/sso?entityId=simplesamlphp"))
|
||||
.andExpect(MockMvcResultMatchers.redirectedUrl("/custom/auth/sso?entityId=simplesamlphp"))
|
||||
request.queryParam("entityId", registration.registrationId)
|
||||
val result: MvcResult =
|
||||
this.mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().isFound()).andReturn()
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ class TokenEndpointDslTests {
|
||||
.state("test")
|
||||
.clientId("clientId")
|
||||
.authorizationUri("https://test")
|
||||
.redirectUri("http://localhost/login/oauth2/code/google")
|
||||
.redirectUri("/login/oauth2/code/google")
|
||||
.attributes(attributes)
|
||||
.build()
|
||||
every {
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ class UserInfoEndpointDslTests {
|
||||
.state("test")
|
||||
.clientId("clientId")
|
||||
.authorizationUri("https://test")
|
||||
.redirectUri("http://localhost/login/oauth2/code/google")
|
||||
.redirectUri("/login/oauth2/code/google")
|
||||
.attributes(attributes)
|
||||
.build()
|
||||
every {
|
||||
|
||||
+4
@@ -28,6 +28,7 @@
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<oauth2-login access-token-response-client-ref="accessTokenResponseClient"
|
||||
user-service-ref="oauth2UserService"
|
||||
oidc-user-service-ref="oidcUserService"
|
||||
user-authorities-mapper-ref="userAuthoritiesMapper"
|
||||
jwt-decoder-factory-ref="jwtDecoderFactory"
|
||||
authorization-request-repository-ref="authorizationRequestRepository"
|
||||
@@ -39,6 +40,9 @@
|
||||
</b:bean>
|
||||
<b:bean id="oauth2UserService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.oauth2.client.userinfo.OAuth2UserService" type="java.lang.Class"/>
|
||||
</b:bean>
|
||||
<b:bean id="oidcUserService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService" type="java.lang.Class"/>
|
||||
</b:bean>
|
||||
<b:bean id="userAuthoritiesMapper" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper" type="java.lang.Class"/>
|
||||
|
||||
+4
@@ -27,6 +27,7 @@
|
||||
<http auto-config="true">
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<oauth2-login access-token-response-client-ref="accessTokenResponseClient"
|
||||
oidc-user-service-ref="oidcUserService"
|
||||
jwt-decoder-factory-ref="jwtDecoderFactory"
|
||||
authorization-request-repository-ref="authorizationRequestRepository"/>
|
||||
<request-cache ref="requestCache" />
|
||||
@@ -34,6 +35,9 @@
|
||||
|
||||
<b:bean id="accessTokenResponseClient" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient" type="java.lang.Class"/>
|
||||
</b:bean>
|
||||
<b:bean id="oidcUserService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService" type="java.lang.Class"/>
|
||||
</b:bean>
|
||||
<b:bean id="jwtDecoderFactory" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.oauth2.jwt.JwtDecoderFactory" type="java.lang.Class"/>
|
||||
|
||||
@@ -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'
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public interface AuthenticationProvider {
|
||||
* </p>
|
||||
* <p>
|
||||
* Selection of an <code>AuthenticationProvider</code> capable of performing
|
||||
* authentication is conducted at runtime the <code>ProviderManager</code>.
|
||||
* authentication is conducted at runtime by the <code>ProviderManager</code>.
|
||||
* </p>
|
||||
* @param authentication
|
||||
* @return <code>true</code> if the implementation can more closely evaluate the
|
||||
|
||||
+4
-5
@@ -99,7 +99,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
private @Nullable RequiredFactorError requiredFactorError(RequiredFactor requiredFactor,
|
||||
List<GrantedAuthority> currentFactors) {
|
||||
Optional<GrantedAuthority> matchingAuthority = currentFactors.stream()
|
||||
.filter((authority) -> authority.getAuthority().equals(requiredFactor.getAuthority()))
|
||||
.filter((authority) -> Objects.equals(authority.getAuthority(), requiredFactor.getAuthority()))
|
||||
.findFirst();
|
||||
if (!matchingAuthority.isPresent()) {
|
||||
return RequiredFactorError.createMissing(requiredFactor);
|
||||
@@ -166,7 +166,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
* @param requiredFactor the {@link Consumer} to invoke.
|
||||
* @return the builder.
|
||||
*/
|
||||
public Builder requireFactor(Consumer<RequiredFactor.Builder> requiredFactor) {
|
||||
public Builder<T> requireFactor(Consumer<RequiredFactor.Builder> requiredFactor) {
|
||||
Assert.notNull(requiredFactor, "requiredFactor cannot be null");
|
||||
RequiredFactor.Builder builder = RequiredFactor.builder();
|
||||
requiredFactor.accept(builder);
|
||||
@@ -178,7 +178,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
* @param requiredFactor the requiredFactor to add. Cannot be null.
|
||||
* @return the builder.
|
||||
*/
|
||||
public Builder requireFactor(RequiredFactor requiredFactor) {
|
||||
public Builder<T> requireFactor(RequiredFactor requiredFactor) {
|
||||
Assert.notNull(requiredFactor, "requiredFactor cannot be null");
|
||||
this.requiredFactors.add(requiredFactor);
|
||||
return this;
|
||||
@@ -186,10 +186,9 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
|
||||
/**
|
||||
* Builds the {@link AllRequiredFactorsAuthorizationManager}.
|
||||
* @param <T> the type.
|
||||
* @return the {@link AllRequiredFactorsAuthorizationManager}
|
||||
*/
|
||||
public <T> AllRequiredFactorsAuthorizationManager<T> build() {
|
||||
public AllRequiredFactorsAuthorizationManager<T> build() {
|
||||
Assert.state(!this.requiredFactors.isEmpty(), "requiredFactors cannot be empty");
|
||||
return new AllRequiredFactorsAuthorizationManager<T>(this.requiredFactors);
|
||||
}
|
||||
|
||||
+5
-2
@@ -17,7 +17,10 @@
|
||||
package org.springframework.security.authorization;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -47,8 +50,8 @@ public class AuthorityReactiveAuthorizationManager<T> implements ReactiveAuthori
|
||||
// @formatter:off
|
||||
return authentication.filter(Authentication::isAuthenticated)
|
||||
.flatMapIterable(Authentication::getAuthorities)
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.any((grantedAuthority) -> this.authorities.stream().anyMatch((authority) -> authority.getAuthority().equals(grantedAuthority)))
|
||||
.mapNotNull((Function<GrantedAuthority, @Nullable String>) GrantedAuthority::getAuthority)
|
||||
.any((grantedAuthority) -> this.authorities.stream().anyMatch((authority) -> Objects.equals(authority.getAuthority(), grantedAuthority)))
|
||||
.map((granted) -> ((AuthorizationResult) new AuthorityAuthorizationDecision(granted, this.authorities)))
|
||||
.defaultIfEmpty(new AuthorityAuthorizationDecision(false, this.authorities));
|
||||
// @formatter:on
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.security.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,6 @@ public interface GrantedAuthority extends Serializable {
|
||||
* granted authority cannot be expressed as a <code>String</code> with sufficient
|
||||
* precision).
|
||||
*/
|
||||
String getAuthority();
|
||||
@Nullable String getAuthority();
|
||||
|
||||
}
|
||||
|
||||
+9
-3
@@ -27,6 +27,7 @@ import org.springframework.util.Assert;
|
||||
* {@link org.springframework.security.core.Authentication Authentication} object.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public final class SimpleGrantedAuthority implements GrantedAuthority {
|
||||
|
||||
@@ -34,9 +35,14 @@ public final class SimpleGrantedAuthority implements GrantedAuthority {
|
||||
|
||||
private final String role;
|
||||
|
||||
public SimpleGrantedAuthority(String role) {
|
||||
Assert.hasText(role, "A granted authority textual representation is required");
|
||||
this.role = role;
|
||||
/**
|
||||
* Constructs a {@code SimpleGrantedAuthority} using the provided authority.
|
||||
* @param authority The provided authority, including any prefix; for example,
|
||||
* {@code ROLE_ADMIN}
|
||||
*/
|
||||
public SimpleGrantedAuthority(String authority) {
|
||||
Assert.hasText(authority, "A granted authority textual representation is required");
|
||||
this.role = authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-1
@@ -64,7 +64,10 @@ public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper, In
|
||||
public Set<GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
|
||||
HashSet<GrantedAuthority> mapped = new HashSet<>(authorities.size());
|
||||
for (GrantedAuthority authority : authorities) {
|
||||
mapped.add(mapAuthority(authority.getAuthority()));
|
||||
String authorityStr = authority.getAuthority();
|
||||
if (authorityStr != null) {
|
||||
mapped.add(mapAuthority(authorityStr));
|
||||
}
|
||||
}
|
||||
if (this.defaultAuthority != null) {
|
||||
mapped.add(this.defaultAuthority);
|
||||
|
||||
+57
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -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(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class);
|
||||
context.setMixIn(BadCredentialsException.class, BadCredentialsExceptionMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -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,210 @@
|
||||
/*
|
||||
* 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 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 oauth2AuthorizationServerJacksonModuleClass = "org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule";
|
||||
|
||||
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 oauth2AuthorizationServerPresent;
|
||||
|
||||
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);
|
||||
oauth2AuthorizationServerPresent = ClassUtils
|
||||
.isPresent("org.springframework.security.oauth2.server.authorization.OAuth2Authorization", 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 (oauth2AuthorizationServerPresent) {
|
||||
addToModulesList(loader, modules, oauth2AuthorizationServerJacksonModuleClass);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -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 TestingAuthenticationTokenMixin {
|
||||
|
||||
/**
|
||||
* Constructor used by Jackson to create object of
|
||||
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken}.
|
||||
* {@link org.springframework.security.authentication.AnonymousAuthenticationToken#AnonymousAuthenticationToken(String, Object, Collection)}
|
||||
* @param principal the principal (typically a <code>UserDetails</code>)
|
||||
* @param credentials the credentials
|
||||
* @param authorities the authorities granted to the principal
|
||||
*/
|
||||
@JsonCreator
|
||||
TestingAuthenticationTokenMixin(@JsonProperty("principal") Object principal,
|
||||
@JsonProperty("credentials") Object credentials,
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
+105
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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;
|
||||
+5
@@ -38,7 +38,12 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
* @param <T> the type of the unmodifiable collection, such as {@link List} or
|
||||
* {@link Set}.
|
||||
* @author Hyunmin Choi
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.AbstractUnmodifiableCollectionDeserializer}
|
||||
* based on Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
abstract class AbstractUnmodifiableCollectionDeserializer<T> extends JsonDeserializer<T> {
|
||||
|
||||
@Override
|
||||
|
||||
+5
@@ -43,11 +43,16 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.AnonymousAuthenticationTokenMixin} based on
|
||||
* Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
class AnonymousAuthenticationTokenMixin {
|
||||
|
||||
/**
|
||||
|
||||
+5
@@ -38,9 +38,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
* @author Yannick Lombardi
|
||||
* @since 5.0
|
||||
* @see CoreJackson2Module
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.BadCredentialsExceptionMixin} based on
|
||||
* Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace", "authenticationRequest" })
|
||||
@Deprecated(forRemoval = true)
|
||||
class BadCredentialsExceptionMixin {
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,11 +44,14 @@ import org.springframework.security.core.userdetails.User;
|
||||
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
|
||||
* of all security modules.</b>
|
||||
*
|
||||
* @author Jitendra Singh.
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@link org.springframework.security.jackson.CoreJacksonModule} based on Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@SuppressWarnings({ "serial", "removal" })
|
||||
@Deprecated(forRemoval = true)
|
||||
public class CoreJackson2Module extends SimpleModule {
|
||||
|
||||
public CoreJackson2Module() {
|
||||
|
||||
+6
@@ -37,7 +37,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
* @since 7.0
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.FactorGrantedAuthorityMixin} based on
|
||||
* Jackson 3
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
|
||||
+5
@@ -50,11 +50,16 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.RememberMeAuthenticationTokenMixin} based
|
||||
* on Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
class RememberMeAuthenticationTokenMixin {
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -67,9 +67,12 @@ import org.springframework.util.ClassUtils;
|
||||
* mapper.registerModule(new Saml2Jackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Jitendra Singh.
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@link org.springframework.security.jackson.SecurityJacksonModules} based on Jackson 3
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public final class SecurityJackson2Modules {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SecurityJackson2Modules.class);
|
||||
|
||||
+5
@@ -35,11 +35,16 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
* @since 4.2
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0 in favor of
|
||||
* {@code org.springframework.security.jackson.SimpleGrantedAuthorityMixin} based on
|
||||
* Jackson 3
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract class SimpleGrantedAuthorityMixin {
|
||||
|
||||
/**
|
||||
|
||||
+3
@@ -28,7 +28,10 @@ import java.util.List;
|
||||
* @author Hyunmin Choi
|
||||
* @since 5.0.2
|
||||
* @see UnmodifiableListMixin
|
||||
* @deprecated as of 7.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
class UnmodifiableListDeserializer extends AbstractUnmodifiableCollectionDeserializer<List> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,9 +36,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* @see UnmodifiableListDeserializer
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @deprecated as of 7.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = UnmodifiableListDeserializer.class)
|
||||
@Deprecated(forRemoval = true)
|
||||
class UnmodifiableListMixin {
|
||||
|
||||
/**
|
||||
|
||||
+3
@@ -33,7 +33,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @author Ulrich Grave
|
||||
* @since 5.7
|
||||
* @see UnmodifiableMapMixin
|
||||
* @deprecated as of 7.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true)
|
||||
class UnmodifiableMapDeserializer extends JsonDeserializer<Map<?, ?>> {
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user