|
|
|
@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se |
|
|
|
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.security.MessageDigest; |
|
|
|
import java.security.MessageDigest; |
|
|
|
import java.security.PublicKey; |
|
|
|
|
|
|
|
import java.security.interfaces.ECPrivateKey; |
|
|
|
import java.security.interfaces.ECPrivateKey; |
|
|
|
import java.security.interfaces.ECPublicKey; |
|
|
|
import java.security.interfaces.ECPublicKey; |
|
|
|
import java.security.interfaces.RSAPrivateKey; |
|
|
|
import java.security.interfaces.RSAPrivateKey; |
|
|
|
@ -33,6 +32,7 @@ import java.util.Set; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.UUID; |
|
|
|
|
|
|
|
|
|
|
|
import com.nimbusds.jose.jwk.ECKey; |
|
|
|
import com.nimbusds.jose.jwk.ECKey; |
|
|
|
|
|
|
|
import com.nimbusds.jose.jwk.JWK; |
|
|
|
import com.nimbusds.jose.jwk.JWKSet; |
|
|
|
import com.nimbusds.jose.jwk.JWKSet; |
|
|
|
import com.nimbusds.jose.jwk.RSAKey; |
|
|
|
import com.nimbusds.jose.jwk.RSAKey; |
|
|
|
import com.nimbusds.jose.jwk.source.JWKSource; |
|
|
|
import com.nimbusds.jose.jwk.source.JWKSource; |
|
|
|
@ -89,6 +89,8 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
|
|
|
|
|
|
|
|
private static final ECPrivateKey CLIENT_EC_PRIVATE_KEY = (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate(); |
|
|
|
private static final ECPrivateKey CLIENT_EC_PRIVATE_KEY = (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final ECKey CLIENT_EC_KEY = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); |
|
|
|
|
|
|
|
|
|
|
|
private static NimbusJwtEncoder providerJwtEncoder; |
|
|
|
private static NimbusJwtEncoder providerJwtEncoder; |
|
|
|
|
|
|
|
|
|
|
|
private static NimbusJwtEncoder clientJwtEncoder; |
|
|
|
private static NimbusJwtEncoder clientJwtEncoder; |
|
|
|
@ -104,9 +106,8 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
JWKSource<SecurityContext> providerJwkSource = (jwkSelector, securityContext) -> jwkSelector |
|
|
|
JWKSource<SecurityContext> providerJwkSource = (jwkSelector, securityContext) -> jwkSelector |
|
|
|
.select(new JWKSet(providerRsaKey)); |
|
|
|
.select(new JWKSet(providerRsaKey)); |
|
|
|
providerJwtEncoder = new NimbusJwtEncoder(providerJwkSource); |
|
|
|
providerJwtEncoder = new NimbusJwtEncoder(providerJwkSource); |
|
|
|
ECKey clientEcKey = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); |
|
|
|
|
|
|
|
JWKSource<SecurityContext> clientJwkSource = (jwkSelector, securityContext) -> jwkSelector |
|
|
|
JWKSource<SecurityContext> clientJwkSource = (jwkSelector, securityContext) -> jwkSelector |
|
|
|
.select(new JWKSet(clientEcKey)); |
|
|
|
.select(new JWKSet(CLIENT_EC_KEY)); |
|
|
|
clientJwtEncoder = new NimbusJwtEncoder(clientJwkSource); |
|
|
|
clientJwtEncoder = new NimbusJwtEncoder(clientJwkSource); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -114,7 +115,7 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
public void requestWhenDPoPAndBearerAuthenticationThenUnauthorized() throws Exception { |
|
|
|
public void requestWhenDPoPAndBearerAuthenticationThenUnauthorized() throws Exception { |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
@ -131,7 +132,7 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
public void requestWhenDPoPAccessTokenMalformedThenUnauthorized() throws Exception { |
|
|
|
public void requestWhenDPoPAccessTokenMalformedThenUnauthorized() throws Exception { |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
@ -147,7 +148,7 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
public void requestWhenMultipleDPoPProofsThenUnauthorized() throws Exception { |
|
|
|
public void requestWhenMultipleDPoPProofsThenUnauthorized() throws Exception { |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
@ -164,7 +165,7 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
public void requestWhenDPoPAuthenticationValidThenAccessed() throws Exception { |
|
|
|
public void requestWhenDPoPAuthenticationValidThenAccessed() throws Exception { |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
Set<String> scope = Collections.singleton("resource1.read"); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); |
|
|
|
String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
this.mvc.perform(get("/resource1") |
|
|
|
@ -175,11 +176,11 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static String generateAccessToken(Set<String> scope, PublicKey clientPublicKey) { |
|
|
|
private static String generateAccessToken(Set<String> scope, JWK jwk) { |
|
|
|
Map<String, Object> jktClaim = null; |
|
|
|
Map<String, Object> jktClaim = null; |
|
|
|
if (clientPublicKey != null) { |
|
|
|
if (jwk != null) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
String sha256Thumbprint = computeSHA256(clientPublicKey); |
|
|
|
String sha256Thumbprint = jwk.toPublicJWK().computeThumbprint().toString(); |
|
|
|
jktClaim = new HashMap<>(); |
|
|
|
jktClaim = new HashMap<>(); |
|
|
|
jktClaim.put("jkt", sha256Thumbprint); |
|
|
|
jktClaim.put("jkt", sha256Thumbprint); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -207,10 +208,7 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
|
|
|
|
|
|
|
|
private static String generateDPoPProof(String method, String resourceUri, String accessToken) throws Exception { |
|
|
|
private static String generateDPoPProof(String method, String resourceUri, String accessToken) throws Exception { |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
Map<String, Object> publicJwk = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY) |
|
|
|
Map<String, Object> publicJwk = CLIENT_EC_KEY.toPublicJWK().toJSONObject(); |
|
|
|
.build() |
|
|
|
|
|
|
|
.toPublicJWK() |
|
|
|
|
|
|
|
.toJSONObject(); |
|
|
|
|
|
|
|
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) |
|
|
|
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) |
|
|
|
.type("dpop+jwt") |
|
|
|
.type("dpop+jwt") |
|
|
|
.jwk(publicJwk) |
|
|
|
.jwk(publicJwk) |
|
|
|
@ -233,12 +231,6 @@ public class DPoPAuthenticationConfigurerTests { |
|
|
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); |
|
|
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static String computeSHA256(PublicKey publicKey) throws Exception { |
|
|
|
|
|
|
|
MessageDigest md = MessageDigest.getInstance("SHA-256"); |
|
|
|
|
|
|
|
byte[] digest = md.digest(publicKey.getEncoded()); |
|
|
|
|
|
|
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Configuration |
|
|
|
@Configuration |
|
|
|
@EnableWebSecurity |
|
|
|
@EnableWebSecurity |
|
|
|
@EnableWebMvc |
|
|
|
@EnableWebMvc |
|
|
|
|