private static JwtDecoder getJwtDecoder() { Map<String, Object> claims = new HashMap<>(); claims.put(IdTokenClaimNames.SUB, "sub123"); claims.put(IdTokenClaimNames.ISS, "http://localhost/iss"); claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d")); claims.put(IdTokenClaimNames.AZP, "clientId"); Jwt jwt = new Jwt("token123", Instant.now(), Instant.now().plusSeconds(3600), Collections.singletonMap("header1", "value1"), claims); JwtDecoder jwtDecoder = mock(JwtDecoder.class); when(jwtDecoder.decode(any())).thenReturn(jwt); return jwtDecoder; } }
private void setUpIdToken(Map<String, Object> claims, Instant issuedAt, Instant expiresAt) { Map<String, Object> headers = new HashMap<>(); headers.put("alg", "RS256"); Jwt idToken = new Jwt("id-token", issuedAt, expiresAt, headers, claims); JwtDecoder jwtDecoder = mock(JwtDecoder.class); when(jwtDecoder.decode(anyString())).thenReturn(idToken); this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder); }
@Test public void authenticateWhenJwtDecodesThenAuthenticationHasAttributesContainedInJwt() { BearerTokenAuthenticationToken token = this.authentication(); Map<String, Object> claims = new HashMap<>(); claims.put("name", "value"); Jwt jwt = this.jwt(claims); when(this.jwtDecoder.decode("token")).thenReturn(jwt); when(this.jwtAuthenticationConverter.convert(jwt)).thenReturn(new JwtAuthenticationToken(jwt)); JwtAuthenticationToken authentication = (JwtAuthenticationToken) this.provider.authenticate(token); assertThat(authentication.getTokenAttributes()).isEqualTo(claims); }
@Test public void authenticateWhenConverterReturnsAuthenticationThenProviderPropagatesIt() { BearerTokenAuthenticationToken token = this.authentication(); Object details = mock(Object.class); token.setDetails(details); Jwt jwt = this.jwt(Collections.singletonMap("some", "value")); JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt); when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt); when(this.jwtAuthenticationConverter.convert(jwt)).thenReturn(authentication); assertThat(this.provider.authenticate(token)) .isEqualTo(authentication) .hasFieldOrPropertyWithValue("details", details); }
@Test public void authenticateWhenIdTokenValidationErrorThenThrowOAuth2AuthenticationException() { this.exception.expect(OAuth2AuthenticationException.class); this.exception.expectMessage(containsString("[invalid_id_token] ID Token Validation Error")); JwtDecoder jwtDecoder = mock(JwtDecoder.class); when(jwtDecoder.decode(anyString())).thenThrow(new JwtException("ID Token Validation Error")); this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder); this.authenticationProvider.authenticate( new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange)); }
@Test public void authenticateWhenDecoderThrowsIncompatibleErrorMessageThenWrapsWithGenericOne() { BearerTokenAuthenticationToken token = this.authentication(); when(this.jwtDecoder.decode(token.getToken())).thenThrow(new JwtException("with \"invalid\" chars")); assertThatCode(() -> this.provider.authenticate(token)) .isInstanceOf(OAuth2AuthenticationException.class) .hasFieldOrPropertyWithValue( "error.description", "An error occurred while attempting to decode the Jwt: Invalid token"); }
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 authenticateWhenJwtDecodeFailsThenRespondsWithInvalidToken() { BearerTokenAuthenticationToken token = this.authentication(); when(this.jwtDecoder.decode("token")).thenThrow(JwtException.class); assertThatCode(() -> this.provider.authenticate(token)) .matches(failed -> failed instanceof OAuth2AuthenticationException) .matches(errorCode(BearerTokenErrorCodes.INVALID_TOKEN)); }
@Test public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseAuthoritiesArePropagated() throws Exception { this.spring.register(JwtDecoderConfig.class, CustomAuthorityMappingConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(JWT_TOKEN)).thenReturn(JWT); this.mvc.perform(get("/requires-read-scope") .with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()); }
@Test public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed() throws Exception { this.spring.register(CustomJwtDecoderAsBean.class, BasicController.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); }
@Test public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception { this.spring.register(RealmNameConfiguredOnAccessDeniedHandler.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(bearerToken("insufficiently_scoped"))) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); }
@Test public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring.register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(bearerToken(JWT_TOKEN)) .param("access_token", JWT_TOKEN)) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); }
@Test public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { this.spring.register(RealmNameConfiguredOnEntryPoint.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenThrow(JwtException.class); this.mvc.perform(get("/authenticated") .with(bearerToken("invalid_token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); }
@Test public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception { this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire(); CustomJwtDecoderOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderOnDsl.class); JwtDecoder decoder = config.decoder(); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); }
@Test public void issuerWhenResponseIsTypicalThenReturnedDecoderValidatesIssuer() { prepareOpenIdConfigurationResponse(); this.server.enqueue(new MockResponse().setBody(JWK_SET)); JwtDecoder decoder = JwtDecoders.fromOidcIssuerLocation(this.issuer); assertThatCode(() -> decoder.decode(ISSUER_MISMATCH)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("This iss claim is not equal to the configured issuer"); }
@Test public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(post("/authenticated") .param("access_token", JWT_TOKEN) .with(bearerToken(JWT_TOKEN)) .with(csrf())) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); }
@Test public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception { this.spring.register(FormAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenThrow(JwtException.class); MvcResult result = this.mvc.perform(get("/authenticated")) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/login")) .andReturn(); assertThat(result.getRequest().getSession(false)).isNotNull(); result = this.mvc.perform(get("/authenticated") .with(bearerToken("token"))) .andExpect(status().isUnauthorized()) .andReturn(); assertThat(result.getRequest().getSession(false)).isNull(); }
@Test public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception { this.spring.register(ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(httpBasic("basic-user", "basic-password"))) .andExpect(status().isForbidden()) .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); this.mvc.perform(get("/authenticated") .with(bearerToken("insufficiently_scoped"))) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); }
@Test public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() throws Exception { this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenReturn(JWT); this.mvc.perform(get("/authenticated") .with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); this.mvc.perform(post("/authenticated") .param("access_token", JWT_TOKEN)) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); }
@Test public void requestWhenBasicAndResourceServerEntryPointsThenMatchedByRequest() throws Exception { this.spring.register(BasicAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); when(decoder.decode(anyString())).thenThrow(JwtException.class); this.mvc.perform(get("/authenticated") .with(httpBasic("some", "user"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); this.mvc.perform(get("/authenticated")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); this.mvc.perform(get("/authenticated") .with(bearerToken("invalid_token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); }