/** * Constructs a {@code ClientAuthorizationRequiredException} using the provided parameters. * * @param clientRegistrationId the identifier for the client's registration */ public ClientAuthorizationRequiredException(String clientRegistrationId) { super(new OAuth2Error(CLIENT_AUTHORIZATION_REQUIRED_ERROR_CODE, "Authorization required for Client Registration Id: " + clientRegistrationId, null)); Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); this.clientRegistrationId = clientRegistrationId; }
@Override public OAuth2Error convert(Map<String, String> parameters) { String errorCode = parameters.get(OAuth2ParameterNames.ERROR); String errorDescription = parameters.get(OAuth2ParameterNames.ERROR_DESCRIPTION); String errorUri = parameters.get(OAuth2ParameterNames.ERROR_URI); return new OAuth2Error(errorCode, errorDescription, errorUri); } }
private static OAuth2Error invalidIdToken(Map<String, Object> invalidClaims) { String claimsDetail = invalidClaims.entrySet().stream() .map(it -> it.getKey() + " (" + it.getValue() + ")") .collect(Collectors.joining(", ")); return new OAuth2Error("invalid_id_token", "The ID Token contains invalid claims: " + claimsDetail, "https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation"); }
private <T> Mono<T> oauth2AuthorizationException(String errorCode) { return Mono.defer(() -> { OAuth2Error oauth2Error = new OAuth2Error(errorCode); return Mono.error(new OAuth2AuthorizationException(oauth2Error)); }); }
@Test(expected = IllegalArgumentException.class) public void constructorWhenErrorCodeIsNullThenThrowIllegalArgumentException() { new OAuth2Error(null, ERROR_DESCRIPTION, ERROR_URI); }
private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) { if (!OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest)) { return Mono.empty(); } return this.oauth2UserService.loadUser(userRequest) .map(OAuth2User::getAttributes) .map(OidcUserInfo::new) .doOnNext(userInfo -> { String subject = userInfo.getSubject(); if (subject == null || !subject.equals(userRequest.getIdToken().getSubject())) { OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } }); }
private static TokenResponse parse(Map<String, Object> json) { try { return TokenResponse.parse(new JSONObject(json)); } catch (ParseException pe) { OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, "An error occurred parsing the Access Token response: " + pe.getMessage(), null); throw new OAuth2AuthorizationException(oauth2Error, pe); } }
@Test public void constructorWhenAllParametersProvidedAndValidThenCreated() { OAuth2Error error = new OAuth2Error(ERROR_CODE, ERROR_DESCRIPTION, ERROR_URI); assertThat(error.getErrorCode()).isEqualTo(ERROR_CODE); assertThat(error.getDescription()).isEqualTo(ERROR_DESCRIPTION); assertThat(error.getUri()).isEqualTo(ERROR_URI); } }
@Test public void authenticateWhenOAuth2AuthorizationExceptionThenOAuth2AuthorizationException() { when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.error(() -> new OAuth2AuthorizationException(new OAuth2Error("error")))); assertThatCode(() -> authenticate()) .isInstanceOf(OAuth2AuthorizationException.class); }
@Test public void decodeWhenJwtValidationHasTwoErrorsThenJwtExceptionMessageShowsFirstError() { OAuth2Error firstFailure = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2Error secondFailure = new OAuth2Error("another-error", "another-description", "another-uri"); OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(firstFailure, secondFailure); OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class); when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); this.jwtDecoder.setJwtValidator(jwtValidator); assertThatCode(() -> this.jwtDecoder.decode(SIGNED_JWT)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("mock-description") .hasFieldOrPropertyWithValue("errors", Arrays.asList(firstFailure, secondFailure)); }
@Test public void writeInternalWhenConversionFailsThenThrowHttpMessageNotWritableException() { Converter errorParametersConverter = mock(Converter.class); when(errorParametersConverter.convert(any())).thenThrow(RuntimeException.class); this.messageConverter.setErrorParametersConverter(errorParametersConverter); OAuth2Error oauth2Error = new OAuth2Error("unauthorized_client", "The client is not authorized", "https://tools.ietf.org/html/rfc6749#section-5.2"); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); assertThatThrownBy(() -> this.messageConverter.writeInternal(oauth2Error, outputMessage)) .isInstanceOf(HttpMessageNotWritableException.class) .hasMessageContaining("An error occurred writing the OAuth 2.0 Error"); } }
@Test public void decodeWhenUsingCustomValidatorThenValidatorIsInvoked() { OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); this.decoder.setJwtValidator(jwtValidator); OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(error); when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); assertThatCode(() -> this.decoder.decode(this.messageReadToken).block()) .isInstanceOf(JwtException.class) .hasMessageContaining("mock-description"); }
@Test public void decodeWhenJwtFailsValidationThenReturnsCorrespondingErrorMessage() { OAuth2Error failure = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class); when(jwtValidator.validate(any(Jwt.class))) .thenReturn(OAuth2TokenValidatorResult.failure(failure)); this.jwtDecoder.setJwtValidator(jwtValidator); assertThatCode(() -> this.jwtDecoder.decode(SIGNED_JWT)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("mock-description"); }
@Test public void commenceWhenOAuth2AuthenticationExceptionThenContainsErrorInformation() { OAuth2Error oauthError = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); OAuth2AuthenticationException exception = new OAuth2AuthenticationException(oauthError); this.entryPoint.commence(this.exchange, exception).block(); assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer error=\"invalid_request\""); assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); }
@Test public void commenceWhenOAuth2ErrorCompleteThenContainsErrorInformation() { OAuth2Error oauthError = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "Oops", "https://example.com"); OAuth2AuthenticationException exception = new OAuth2AuthenticationException(oauthError); this.entryPoint.commence(this.exchange, exception).block(); assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer error=\"invalid_request\", error_description=\"Oops\", error_uri=\"https://example.com\""); assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); }
@Test public void writeInternalWhenOAuth2ErrorThenWriteErrorResponse() throws Exception { OAuth2Error oauth2Error = new OAuth2Error("unauthorized_client", "The client is not authorized", "https://tools.ietf.org/html/rfc6749#section-5.2"); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); this.messageConverter.writeInternal(oauth2Error, outputMessage); String errorResponse = outputMessage.getBodyAsString(); assertThat(errorResponse).contains("\"error\":\"unauthorized_client\""); assertThat(errorResponse).contains("\"error_description\":\"The client is not authorized\""); assertThat(errorResponse).contains("\"error_uri\":\"https://tools.ietf.org/html/rfc6749#section-5.2\""); }
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) { JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration); Jwt jwt; try { jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); } catch (JwtException ex) { OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null); throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex); } OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()); return idToken; } }
@Test public void doFilterWhenAuthorizationFailsThenHandleOAuth2AuthorizationException() throws Exception { String requestUri = "/callback/client-1"; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); request.addParameter(OAuth2ParameterNames.CODE, "code"); request.addParameter(OAuth2ParameterNames.STATE, "state"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); this.setUpAuthorizationRequest(request, response, this.registration1); OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT); when(this.authenticationManager.authenticate(any(Authentication.class))) .thenThrow(new OAuth2AuthorizationException(error)); this.filter.doFilter(request, response, filterChain); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1?error=invalid_grant"); }
@Test public void decodeWhenJwtFailsValidationThenReturnsCorrespondingErrorMessage() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); OAuth2Error failure = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class); when(jwtValidator.validate(any(Jwt.class))).thenReturn(OAuth2TokenValidatorResult.failure(failure)); decoder.setJwtValidator(jwtValidator); assertThatCode(() -> decoder.decode(SIGNED_JWT)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("mock-description"); } }
@Test public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() { this.spring.register(CustomAuthenticationManagerConfig.class).autowire(); ReactiveAuthenticationManager authenticationManager = this.spring.getContext().getBean( ReactiveAuthenticationManager.class); when(authenticationManager.authenticate(any(Authentication.class))) .thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); this.client.get() .headers(headers -> headers.setBearerAuth(this.messageReadToken)) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); }