private ReactiveAuthenticationManager authenticationManager() { OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(tokenResponseClient, userService); oidc.setJwtDecoderFactory(jwtDecoderFactory()); return oidc; }
@Test public void authenticateWhenIdTokenValidationErrorThenOAuth2AuthenticationException() { OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo") .tokenType(OAuth2AccessToken.TokenType.BEARER) .additionalParameters(Collections.singletonMap(OidcParameterNames.ID_TOKEN, this.idToken.getTokenValue())) .build(); when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); when(this.jwtDecoder.decode(any())).thenThrow(new JwtException("ID Token Validation Error")); this.manager.setJwtDecoderFactory(c -> this.jwtDecoder); assertThatThrownBy(() -> this.manager.authenticate(loginToken()).block()) .isInstanceOf(OAuth2AuthenticationException.class) .hasMessageContaining("[invalid_id_token] ID Token Validation Error"); }
@Test public void constructorWhenNullAccessTokenResponseClientThenIllegalArgumentException() { this.accessTokenResponseClient = null; assertThatThrownBy(() -> new OidcAuthorizationCodeReactiveAuthenticationManager(this.accessTokenResponseClient, this.userService)) .isInstanceOf(IllegalArgumentException.class); }
@Test public void setJwtDecoderFactoryWhenNullThenIllegalArgumentException() { assertThatThrownBy(() -> this.manager.setJwtDecoderFactory(null)) .isInstanceOf(IllegalArgumentException.class); }
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) { OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration(); Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters(); if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) { OAuth2Error invalidIdTokenError = new OAuth2Error( INVALID_ID_TOKEN_ERROR_CODE, "Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(), null); throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()); } return createOidcToken(clientRegistration, accessTokenResponse) .map(idToken -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters)) .flatMap(this.userService::loadUser) .map(oauth2User -> { Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities()); return new OAuth2LoginAuthenticationToken( authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange(), oauth2User, mappedAuthorities, accessToken, accessTokenResponse.getRefreshToken()); }); }
.flatMap(accessTokenResponse -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse)) .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString())) .onErrorMap(JwtException.class, e -> {
@Test public void authenticateWhenNoSubscriptionThenDoesNothing() { // we didn't do anything because it should cause a ClassCastException (as verified below) TestingAuthenticationToken token = new TestingAuthenticationToken("a", "b"); assertThatCode(()-> this.manager.authenticate(token)) .doesNotThrowAnyException(); assertThatThrownBy(() -> this.manager.authenticate(token).block()) .isInstanceOf(Throwable.class); }
@Test public void constructorWhenNullUserServiceThenIllegalArgumentException() { this.userService = null; assertThatThrownBy(() -> new OidcAuthorizationCodeReactiveAuthenticationManager(this.accessTokenResponseClient, this.userService)) .isInstanceOf(IllegalArgumentException.class); }
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) { OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration(); Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters(); if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) { OAuth2Error invalidIdTokenError = new OAuth2Error( INVALID_ID_TOKEN_ERROR_CODE, "Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(), null); throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()); } return createOidcToken(clientRegistration, accessTokenResponse) .map(idToken -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters)) .flatMap(this.userService::loadUser) .map(oauth2User -> { Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities()); return new OAuth2LoginAuthenticationToken( authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange(), oauth2User, mappedAuthorities, accessToken, accessTokenResponse.getRefreshToken()); }); }
.flatMap(accessTokenResponse -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse)) .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString())); });
@Test public void authenticationWhenStateDoesNotMatchThenOAuth2AuthenticationException() { this.authorizationResponseBldr.state("notmatch"); assertThatThrownBy(() -> this.manager.authenticate(loginToken()).block()) .isInstanceOf(OAuth2AuthenticationException.class); }
private ReactiveAuthenticationManager createDefault() { WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient(); ReactiveAuthenticationManager result = new OAuth2LoginReactiveAuthenticationManager(client, getOauth2UserService()); boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent( "org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader()); if (oidcAuthenticationProviderEnabled) { OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService()); ResolvableType type = ResolvableType.forClassWithGenerics( ReactiveJwtDecoderFactory.class, ClientRegistration.class); ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = getBeanOrNull(type); if (jwtDecoderFactory != null) { oidc.setJwtDecoderFactory(jwtDecoderFactory); } result = new DelegatingReactiveAuthenticationManager(oidc, result); } return result; }
@Before public void setup() { this.manager = new OidcAuthorizationCodeReactiveAuthenticationManager(this.accessTokenResponseClient, this.userService); }
@Test public void authenticationWhenOAuth2UserNotFoundThenEmpty() { OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo") .tokenType(OAuth2AccessToken.TokenType.BEARER) .additionalParameters(Collections.singletonMap(OidcParameterNames.ID_TOKEN, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.")) .build(); Map<String, Object> claims = new HashMap<>(); claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com"); claims.put(IdTokenClaimNames.SUB, "rob"); claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id")); Instant issuedAt = Instant.now(); Instant expiresAt = Instant.from(issuedAt).plusSeconds(3600); Jwt idToken = new Jwt("id-token", issuedAt, expiresAt, claims, claims); when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); when(this.userService.loadUser(any())).thenReturn(Mono.empty()); when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken)); this.manager.setJwtDecoderFactory(c -> this.jwtDecoder); assertThat(this.manager.authenticate(loginToken()).block()).isNull(); }
@Test public void authenticationWhenErrorThenOAuth2AuthenticationException() { this.authorizationResponseBldr = OAuth2AuthorizationResponse .error("error") .state("state"); assertThatThrownBy(() -> this.manager.authenticate(loginToken()).block()) .isInstanceOf(OAuth2AuthenticationException.class); }
private ReactiveAuthenticationManager createDefault() { WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient(); ReactiveAuthenticationManager result = new OAuth2LoginReactiveAuthenticationManager(client, getOauth2UserService()); boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent( "org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader()); if (oidcAuthenticationProviderEnabled) { OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService()); result = new DelegatingReactiveAuthenticationManager(oidc, result); } return result; }
@Test public void authenticationWhenOAuth2UserFoundThenSuccess() { OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo") .tokenType(OAuth2AccessToken.TokenType.BEARER) .additionalParameters(Collections.singletonMap(OidcParameterNames.ID_TOKEN, this.idToken.getTokenValue())) .build(); Map<String, Object> claims = new HashMap<>(); claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com"); claims.put(IdTokenClaimNames.SUB, "rob"); claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id")); Instant issuedAt = Instant.now(); Instant expiresAt = Instant.from(issuedAt).plusSeconds(3600); Jwt idToken = new Jwt("id-token", issuedAt, expiresAt, claims, claims); when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken); when(this.userService.loadUser(any())).thenReturn(Mono.just(user)); when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken)); this.manager.setJwtDecoderFactory(c -> this.jwtDecoder); OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block(); assertThat(result.getPrincipal()).isEqualTo(user); assertThat(result.getAuthorities()).containsOnlyElementsOf(user.getAuthorities()); assertThat(result.isAuthenticated()).isTrue(); }
@Test public void authenticationWhenNotOidcThenEmpty() { this.registration.scope("notopenid"); assertThat(this.manager.authenticate(loginToken()).block()).isNull(); }
private ReactiveAuthenticationManager createDefault() { WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient(); ReactiveAuthenticationManager result = new OAuth2LoginReactiveAuthenticationManager(client, getOauth2UserService()); boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent( "org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader()); if (oidcAuthenticationProviderEnabled) { OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService()); result = new DelegatingReactiveAuthenticationManager(oidc, result); } return result; }
@Test public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToUserRequest() { ClientRegistration clientRegistration = this.registration.build(); Map<String, Object> additionalParameters = new HashMap<>(); additionalParameters.put(OidcParameterNames.ID_TOKEN, this.idToken.getTokenValue()); additionalParameters.put("param1", "value1"); additionalParameters.put("param2", "value2"); OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo") .tokenType(OAuth2AccessToken.TokenType.BEARER) .additionalParameters(additionalParameters) .build(); Map<String, Object> claims = new HashMap<>(); claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com"); claims.put(IdTokenClaimNames.SUB, "rob"); claims.put(IdTokenClaimNames.AUD, Arrays.asList(clientRegistration.getClientId())); Instant issuedAt = Instant.now(); Instant expiresAt = Instant.from(issuedAt).plusSeconds(3600); Jwt idToken = new Jwt("id-token", issuedAt, expiresAt, claims, claims); when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken); ArgumentCaptor<OidcUserRequest> userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class); when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(Mono.just(user)); when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken)); this.manager.setJwtDecoderFactory(c -> this.jwtDecoder); this.manager.authenticate(loginToken()).block(); assertThat(userRequestArgCaptor.getValue().getAdditionalParameters()) .containsAllEntriesOf(accessTokenResponse.getAdditionalParameters()); }