Skip to content

Commit aceb5fa

Browse files
c1rd3cmjzheaux
authored andcommitted
Allow logout+jwt JWT type for reactive
The OIDC back-channel spec recommends using a logout token typ `logout+jwt` (see [here](https://openid.net/specs/openid-connect-backchannel-1_0-final.html#LogoutToken). Support of this type was recently added [on the servlet side]([on the Servlet side](9101bf1)), so back porting the same on the reactive side to close the gap. Closes gh-15702
1 parent 29331a0 commit aceb5fa

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,26 +16,33 @@
1616

1717
package org.springframework.security.config.web.server;
1818

19+
import com.nimbusds.jose.JOSEObjectType;
20+
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
21+
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
22+
import com.nimbusds.jose.proc.JWKSecurityContext;
1923
import reactor.core.publisher.Mono;
2024

2125
import org.springframework.security.authentication.AuthenticationProvider;
2226
import org.springframework.security.authentication.AuthenticationServiceException;
2327
import org.springframework.security.authentication.ReactiveAuthenticationManager;
2428
import org.springframework.security.core.Authentication;
2529
import org.springframework.security.core.AuthenticationException;
26-
import org.springframework.security.oauth2.client.oidc.authentication.ReactiveOidcIdTokenDecoderFactory;
30+
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
2731
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
2832
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2933
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3034
import org.springframework.security.oauth2.core.OAuth2Error;
3135
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
36+
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
3237
import org.springframework.security.oauth2.jwt.BadJwtException;
3338
import org.springframework.security.oauth2.jwt.Jwt;
3439
import org.springframework.security.oauth2.jwt.JwtDecoder;
3540
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
41+
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
3642
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
3743
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
3844
import org.springframework.util.Assert;
45+
import org.springframework.util.StringUtils;
3946

4047
/**
4148
* An {@link AuthenticationProvider} that authenticates an OIDC Logout Token; namely
@@ -61,9 +68,27 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti
6168
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
6269
*/
6370
OidcBackChannelLogoutReactiveAuthenticationManager() {
64-
ReactiveOidcIdTokenDecoderFactory logoutTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
65-
logoutTokenDecoderFactory.setJwtValidatorFactory(new DefaultOidcLogoutTokenValidatorFactory());
66-
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
71+
DefaultOidcLogoutTokenValidatorFactory jwtValidator = new DefaultOidcLogoutTokenValidatorFactory();
72+
this.logoutTokenDecoderFactory = (clientRegistration) -> {
73+
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
74+
if (!StringUtils.hasText(jwkSetUri)) {
75+
OAuth2Error oauth2Error = new OAuth2Error("missing_signature_verifier",
76+
"Failed to find a Signature Verifier for Client Registration: '"
77+
+ clientRegistration.getRegistrationId()
78+
+ "'. Check to ensure you have configured the JwkSet URI.",
79+
null);
80+
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
81+
}
82+
JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null,
83+
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
84+
NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
85+
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
86+
.build();
87+
decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
88+
decoder.setClaimSetConverter(
89+
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
90+
return decoder;
91+
};
6792
}
6893

6994
/**

config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
7676
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
7777
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
78+
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
79+
import org.springframework.security.oauth2.jwt.JwsHeader;
7880
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
7981
import org.springframework.security.oauth2.jwt.JwtEncoder;
8082
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
@@ -819,8 +821,9 @@ String jwks() {
819821
String logoutToken(@AuthenticationPrincipal OidcUser user) {
820822
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
821823
.audience(List.of(this.registration.getClientId())).build();
822-
JwtEncoderParameters parameters = JwtEncoderParameters
823-
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
824+
JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build();
825+
JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build();
826+
JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims);
824827
return this.encoder.encode(parameters).getTokenValue();
825828
}
826829

@@ -829,8 +832,9 @@ String logoutTokenAll(@AuthenticationPrincipal OidcUser user) {
829832
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
830833
.audience(List.of(this.registration.getClientId()))
831834
.claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build();
832-
JwtEncoderParameters parameters = JwtEncoderParameters
833-
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
835+
JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build();
836+
JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build();
837+
JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims);
834838
return this.encoder.encode(parameters).getTokenValue();
835839
}
836840
}

0 commit comments

Comments
 (0)