@ -18,8 +18,10 @@ package org.springframework.security.oauth2.client;
@@ -18,8 +18,10 @@ package org.springframework.security.oauth2.client;
import java.time.Duration ;
import java.time.Instant ;
import java.time.temporal.ChronoUnit ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.LinkedHashSet ;
import java.util.List ;
import java.util.Map ;
import java.util.Set ;
@ -37,6 +39,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
@@ -37,6 +39,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.TestClientRegistrations ;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService ;
import org.springframework.security.oauth2.core.OAuth2AccessToken ;
import org.springframework.security.oauth2.core.oidc.OidcIdToken ;
import org.springframework.security.oauth2.core.oidc.OidcScopes ;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames ;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser ;
@ -99,7 +102,8 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -99,7 +102,8 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@Test
void onAuthorizationSuccessWhenIdTokenValidThenSecurityContextRefreshed ( ) {
ClientRegistration clientRegistration = TestClientRegistrations . clientRegistration ( ) . build ( ) ;
DefaultOidcUser principal = TestOidcUsers . create ( ) ;
Instant authTime = Instant . now ( ) ;
DefaultOidcUser principal = createOidcUser ( authTime ) ;
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken ( principal ,
principal . getAuthorities ( ) , clientRegistration . getRegistrationId ( ) ) ;
OAuth2AccessToken accessToken = createAccessToken ( ) ;
@ -112,6 +116,7 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -112,6 +116,7 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
claims . put ( "iss" , principal . getIssuer ( ) ) ;
claims . put ( "sub" , principal . getSubject ( ) ) ;
claims . put ( "aud" , principal . getAudience ( ) ) ;
claims . put ( "auth_time" , authTime ) ;
claims . put ( "nonce" , principal . getNonce ( ) ) ;
Jwt jwt = mock ( Jwt . class ) ;
given ( jwt . getTokenValue ( ) ) . willReturn ( "id-token-1234" ) ;
@ -316,9 +321,10 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -316,9 +321,10 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
}
@Test
void onAuthorizationSuccessWhenIdTokenAuthTimeNotSa meThenException ( ) {
void onAuthorizationSuccessWhenIdTokenAuthTimeBeforeCurrentAuthTi meThenException ( ) {
ClientRegistration clientRegistration = TestClientRegistrations . clientRegistration ( ) . build ( ) ;
DefaultOidcUser principal = TestOidcUsers . create ( ) ;
Instant authTime = Instant . now ( ) ;
DefaultOidcUser principal = createOidcUser ( authTime ) ;
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken ( principal ,
principal . getAuthorities ( ) , clientRegistration . getRegistrationId ( ) ) ;
OAuth2AccessToken accessToken = createAccessToken ( ) ;
@ -331,7 +337,7 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -331,7 +337,7 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
claims . put ( "iss" , principal . getIssuer ( ) ) ;
claims . put ( "sub" , principal . getSubject ( ) ) ;
claims . put ( "aud" , principal . getAudience ( ) ) ;
claims . put ( "auth_time" , principal . getIssuedAt ( ) ) ;
claims . put ( "auth_time" , authTime . minus ( 5 , ChronoUnit . MINUTES ) ) ;
claims . put ( "nonce" , principal . getNonce ( ) ) ;
Jwt jwt = mock ( Jwt . class ) ;
given ( jwt . getTokenValue ( ) ) . willReturn ( "id-token-1234" ) ;
@ -352,6 +358,47 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -352,6 +358,47 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
. verifyErrorMessage ( "[invalid_id_token] Invalid authenticated at time" ) ;
}
@Test
void onAuthorizationSuccessWhenIdTokenAuthTimeAfterCurrentAuthTimeThenSecurityContextRefreshed ( ) {
ClientRegistration clientRegistration = TestClientRegistrations . clientRegistration ( ) . build ( ) ;
Instant authTime = Instant . now ( ) ;
DefaultOidcUser principal = createOidcUser ( authTime ) ;
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken ( principal ,
principal . getAuthorities ( ) , clientRegistration . getRegistrationId ( ) ) ;
OAuth2AccessToken accessToken = createAccessToken ( ) ;
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient ( clientRegistration , principal . getName ( ) ,
accessToken , null ) ;
MockServerWebExchange exchange = MockServerWebExchange . from ( MockServerHttpRequest . get ( "/" ) . build ( ) ) ;
Map < String , Object > attributes = Map . of ( ServerWebExchange . class . getName ( ) , exchange ,
OidcParameterNames . ID_TOKEN , "id-token-1234" ) ;
Map < String , Object > claims = new HashMap < > ( ) ;
claims . put ( "iss" , principal . getIssuer ( ) ) ;
claims . put ( "sub" , principal . getSubject ( ) ) ;
claims . put ( "aud" , principal . getAudience ( ) ) ;
claims . put ( "auth_time" , authTime . plus ( 5 , ChronoUnit . MINUTES ) ) ;
claims . put ( "nonce" , principal . getNonce ( ) ) ;
Jwt jwt = mock ( Jwt . class ) ;
given ( jwt . getTokenValue ( ) ) . willReturn ( "id-token-1234" ) ;
given ( jwt . getIssuedAt ( ) ) . willReturn ( principal . getIssuedAt ( ) ) ;
given ( jwt . getClaims ( ) ) . willReturn ( claims ) ;
ReactiveJwtDecoder jwtDecoder = mock ( ReactiveJwtDecoder . class ) ;
given ( jwtDecoder . decode ( any ( ) ) ) . willReturn ( Mono . just ( jwt ) ) ;
ReactiveJwtDecoderFactory < ClientRegistration > reactiveJwtDecoderFactory = mock ( ReactiveJwtDecoderFactory . class ) ;
given ( reactiveJwtDecoderFactory . createDecoder ( any ( ) ) ) . willReturn ( jwtDecoder ) ;
ReactiveOAuth2UserService < OidcUserRequest , OidcUser > userService = mock ( ReactiveOAuth2UserService . class ) ;
given ( userService . loadUser ( any ( ) ) ) . willReturn ( Mono . just ( principal ) ) ;
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository ( ) ;
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler ( ) ;
handler . setJwtDecoderFactory ( reactiveJwtDecoderFactory ) ;
handler . setUserService ( userService ) ;
handler . setServerSecurityContextRepository ( serverSecurityContextRepository ) ;
StepVerifier . create ( handler . onAuthorizationSuccess ( authorizedClient , authenticationToken , attributes ) )
. verifyComplete ( ) ;
StepVerifier . create ( serverSecurityContextRepository . load ( exchange ) . map ( SecurityContext : : getAuthentication ) )
. expectNext ( authenticationToken )
. verifyComplete ( ) ;
}
@Test
void onAuthorizationSuccessWhenIdTokenNonceNotSameThenException ( ) {
ClientRegistration clientRegistration = TestClientRegistrations . clientRegistration ( ) . build ( ) ;
@ -395,4 +442,21 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
@@ -395,4 +442,21 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
Set . of ( OidcScopes . OPENID ) ) ;
}
private static DefaultOidcUser createOidcUser ( Instant authTime ) {
Instant issuedAt = Instant . now ( ) ;
Instant expiresAt = issuedAt . plusSeconds ( 3600 ) ;
// @formatter:off
OidcIdToken idToken = OidcIdToken . withTokenValue ( "id-token" )
. issuedAt ( issuedAt )
. expiresAt ( expiresAt )
. subject ( "subject" )
. issuer ( "http://localhost/issuer" )
. audience ( Collections . unmodifiableSet ( new LinkedHashSet < > ( Collections . singletonList ( "client-id" ) ) ) )
. authorizedParty ( "client" )
. authTime ( authTime )
. build ( ) ;
// @formatter:on
return new DefaultOidcUser ( null , idToken ) ;
}
}