@Override public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { Assert.notNull(userRequest, "userRequest cannot be null"); return getUserInfo(userRequest) .map(userInfo -> new OidcUserAuthority(userRequest.getIdToken(), userInfo)) .defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null)) .map(authority -> { OidcUserInfo userInfo = authority.getUserInfo(); Set<GrantedAuthority> authorities = new HashSet<>(); authorities.add(authority); String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); if (StringUtils.hasText(userNameAttributeName)) { return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); } else { return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); } }); }
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { // Auto-disabled if UserInfo Endpoint URI is not provided if (StringUtils.isEmpty(userRequest.getClientRegistration().getProviderDetails() .getUserInfoEndpoint().getUri())) { return false; } // The Claims requested by the profile, email, address, and phone scope values // are returned from the UserInfo Endpoint (as described in Section 5.3.2), // when a response_type value is used that results in an Access Token being issued. // However, when no Access Token is issued, which is the case for the response_type=id_token, // the resulting Claims are returned in the ID Token. // The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued. if (AuthorizationGrantType.AUTHORIZATION_CODE.equals( userRequest.getClientRegistration().getAuthorizationGrantType())) { // Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s) return CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.userInfoScopes); } return false; }
@Test public void constructorWhenAllParametersProvidedAndValidThenCreated() { OidcUserRequest userRequest = new OidcUserRequest( this.clientRegistration, this.accessToken, this.idToken, this.additionalParameters); assertThat(userRequest.getClientRegistration()).isEqualTo(this.clientRegistration); assertThat(userRequest.getAccessToken()).isEqualTo(this.accessToken); assertThat(userRequest.getIdToken()).isEqualTo(this.idToken); assertThat(userRequest.getAdditionalParameters()).containsAllEntriesOf(this.additionalParameters); } }
private OidcClient getOidcClient(OidcUserRequest userRequest) { String registrationId = userRequest.getClientRegistration().getRegistrationId(); OidcClient oidcClient = dataService.findOneById(OidcClientMetadata.OIDC_CLIENT, registrationId, OidcClient.class); if (oidcClient == null) { throw new UnknownEntityException(OidcClientMetadata.OIDC_CLIENT, registrationId); } return oidcClient; } }
@Test public void constructorWhenClientRegistrationIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new OidcUserRequest(null, this.accessToken, this.idToken)) .isInstanceOf(IllegalArgumentException.class); }
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()); } }); }
@Test public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToUserRequest() { Map<String, Object> claims = new HashMap<>(); claims.put(IdTokenClaimNames.ISS, "https://provider.com"); claims.put(IdTokenClaimNames.SUB, "subject1"); claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2")); claims.put(IdTokenClaimNames.AZP, "client1"); this.setUpIdToken(claims); OidcUser principal = mock(OidcUser.class); List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); when(principal.getAuthorities()).thenAnswer( (Answer<List<GrantedAuthority>>) invocation -> authorities); ArgumentCaptor<OidcUserRequest> userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class); when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(principal); this.authenticationProvider.authenticate(new OAuth2LoginAuthenticationToken( this.clientRegistration, this.authorizationExchange)); assertThat(userRequestArgCaptor.getValue().getAdditionalParameters()).containsAllEntriesOf( this.accessTokenResponse.getAdditionalParameters()); }
/** package-private for testability */ private String getUserNameAttributeName(OidcUserRequest userRequest) { return userRequest .getClientRegistration() .getProviderDetails() .getUserInfoEndpoint() .getUserNameAttributeName(); } }
@Test public void constructorWhenIdTokenIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new OidcUserRequest(this.clientRegistration, this.accessToken, null)) .isInstanceOf(IllegalArgumentException.class); }
/** * Augments {@link OidcUserService#loadUser(OidcUserRequest)} to add authorities * provided by Keycloak. * * Needed because {@link OidcUserService#loadUser(OidcUserRequest)} (currently) * does not provide a hook for adding custom authorities from a * {@link OidcUserRequest}. */ @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { OidcUser user = super.loadUser(userRequest); Set<GrantedAuthority> authorities = new LinkedHashSet<>(); authorities.addAll(user.getAuthorities()); authorities.addAll(extractKeycloakAuthorities(userRequest)); return new DefaultOidcUser(authorities, userRequest.getIdToken(), user.getUserInfo(), "preferred_username"); }
@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()); }
if (!userInfo.getSubject().equals(userRequest.getIdToken().getSubject())) { OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); new OidcUserAuthority(userRequest.getIdToken(), userInfo)); String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); if (StringUtils.hasText(userNameAttributeName)) { user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); } else { user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
ClientRegistration clientRegistration = userRequest.getClientRegistration(); if (StringUtils.isEmpty(clientRegistration.getProviderDetails() .getUserInfoEndpoint().getUri())) { .containsAny(userRequest.getAccessToken().getScopes(), userRequest.getClientRegistration().getScopes());
private Optional<User> getUser(OidcUser oidcUser, OidcUserRequest userRequest) { OidcUserMapping oidcUserMapping = dataService .query(OIDC_USER_MAPPING, OidcUserMapping.class) .eq(OIDC_CLIENT, userRequest.getClientRegistration().getRegistrationId()) .and() .eq(OIDC_USERNAME, oidcUser.getSubject()) .findOne(); return oidcUserMapping != null ? Optional.of(oidcUserMapping.getUser()) : Optional.empty(); }
@Test public void constructorWhenAccessTokenIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new OidcUserRequest(this.clientRegistration, null, this.idToken)) .isInstanceOf(IllegalArgumentException.class); }
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()); } }); }
@Override public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { Assert.notNull(userRequest, "userRequest cannot be null"); return getUserInfo(userRequest) .map(userInfo -> new OidcUserAuthority(userRequest.getIdToken(), userInfo)) .defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null)) .map(authority -> { OidcUserInfo userInfo = authority.getUserInfo(); Set<GrantedAuthority> authorities = new HashSet<>(); authorities.add(authority); String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); if (StringUtils.hasText(userNameAttributeName)) { return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); } else { return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); } }); }
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { // Auto-disabled if UserInfo Endpoint URI is not provided if (StringUtils.isEmpty(userRequest.getClientRegistration().getProviderDetails() .getUserInfoEndpoint().getUri())) { return false; } // The Claims requested by the profile, email, address, and phone scope values // are returned from the UserInfo Endpoint (as described in Section 5.3.2), // when a response_type value is used that results in an Access Token being issued. // However, when no Access Token is issued, which is the case for the response_type=id_token, // the resulting Claims are returned in the ID Token. // The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued. if (AuthorizationGrantType.AUTHORIZATION_CODE.equals( userRequest.getClientRegistration().getAuthorizationGrantType())) { // Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s) return CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.userInfoScopes); } return false; }
@Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { OidcUser oidcUser = super.loadUser(userRequest); LemonPrincipal principal = oauth2UserService.buildPrincipal(oidcUser, userRequest.getClientRegistration().getRegistrationId()); principal.setClaims(oidcUser.getClaims()); principal.setIdToken(oidcUser.getIdToken()); principal.setUserInfo(oidcUser.getUserInfo()); return principal; } }
private OidcUserRequest userRequest() { return new OidcUserRequest(this.registration.build(), this.accessToken, this.idToken); } }