mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 01:02:14 +00:00
Add InMemoryReactiveOAuth2AuthorizedClientService
Issue: gh-4807
This commit is contained in:
parent
a02b0c17f8
commit
5e9c714ff0
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizedClientService} that stores
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)} in-memory.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizedClientService
|
||||
* @see OAuth2AuthorizedClient
|
||||
* @see ClientRegistration
|
||||
* @see Authentication
|
||||
*/
|
||||
public final class InMemoryReactiveOAuth2AuthorizedClientService implements ReactiveOAuth2AuthorizedClientService {
|
||||
private final Map<String, OAuth2AuthorizedClient> authorizedClients = new ConcurrentHashMap<>();
|
||||
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizedClientService} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
*/
|
||||
public InMemoryReactiveOAuth2AuthorizedClientService(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return (Mono<T>) getIdentifier(clientRegistrationId, principalName)
|
||||
.flatMap(identifier -> Mono.justOrEmpty(this.authorizedClients.get(identifier)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
return Mono.fromRunnable(() -> {
|
||||
String identifier = this.getIdentifier(authorizedClient.getClientRegistration(), principal.getName());
|
||||
this.authorizedClients.put(identifier, authorizedClient);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return this.getIdentifier(clientRegistrationId, principalName)
|
||||
.doOnNext(identifier -> this.authorizedClients.remove(identifier))
|
||||
.then(Mono.empty());
|
||||
}
|
||||
|
||||
private Mono<String> getIdentifier(String clientRegistrationId, String principalName) {
|
||||
return this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
|
||||
.map(registration -> getIdentifier(registration, principalName));
|
||||
}
|
||||
|
||||
private String getIdentifier(ClientRegistration registration, String principalName) {
|
||||
String identifier = "[" + registration.getRegistrationId() + "][" + principalName + "]";
|
||||
return Base64.getEncoder().encodeToString(identifier.getBytes());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the management
|
||||
* of {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose
|
||||
* of associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} credential
|
||||
* to a {@link OAuth2AuthorizedClient#getClientRegistration() Client} and Resource Owner,
|
||||
* who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal}
|
||||
* that originally granted the authorization.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizedClient
|
||||
* @see ClientRegistration
|
||||
* @see Authentication
|
||||
* @see OAuth2AccessToken
|
||||
*/
|
||||
public interface ReactiveOAuth2AuthorizedClientService {
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name
|
||||
* or {@code null} if not available.
|
||||
*
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
* @param <T> a type of OAuth2AuthorizedClient
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if not available
|
||||
*/
|
||||
<T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId,
|
||||
String principalName);
|
||||
|
||||
/**
|
||||
* Saves the {@link OAuth2AuthorizedClient} associating it to
|
||||
* the provided End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* @param authorizedClient the authorized client
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
*/
|
||||
Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient,
|
||||
Authentication principal);
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name.
|
||||
*
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
*/
|
||||
Mono<Void> removeAuthorizedClient(String clientRegistrationId, String principalName);
|
||||
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class InMemoryReactiveOAuth2AuthorizedClientServiceTests {
|
||||
@Mock
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private InMemoryReactiveOAuth2AuthorizedClientService authorizedClientService;
|
||||
|
||||
private String clientRegistrationId = "github";
|
||||
|
||||
private String principalName = "username";
|
||||
|
||||
private Authentication principal = new TestingAuthenticationToken(this.principalName, "notused");
|
||||
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"token",
|
||||
Instant.now(),
|
||||
Instant.now().plus(Duration.ofDays(1)));
|
||||
|
||||
private ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(this.clientRegistrationId)
|
||||
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read:user")
|
||||
.authorizationUri("https://github.com/login/oauth/authorize")
|
||||
.tokenUri("https://github.com/login/oauth/access_token")
|
||||
.userInfoUri("https://api.github.com/user")
|
||||
.userNameAttributeName("id")
|
||||
.clientName("GitHub")
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret")
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.authorizedClientService = new InMemoryReactiveOAuth2AuthorizedClientService(
|
||||
this.clientRegistrationRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullClientRegistrationRepositoryThenThrowsIllegalArgumentException() {
|
||||
this.clientRegistrationRepository = null;
|
||||
assertThatThrownBy(() -> new InMemoryReactiveOAuth2AuthorizedClientService(this.clientRegistrationRepository))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenClientRegistrationIdNullThenIllegalArgumentException() {
|
||||
this.clientRegistrationId = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenClientRegistrationIdEmptyThenIllegalArgumentException() {
|
||||
this.clientRegistrationId = "";
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenPrincipalNameNullThenIllegalArgumentException() {
|
||||
this.principalName = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenPrincipalNameEmptyThenIllegalArgumentException() {
|
||||
this.principalName = "";
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenClientRegistrationIdNotFoundThenEmpty() {
|
||||
when(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId))
|
||||
.thenReturn(Mono.empty());
|
||||
StepVerifier
|
||||
.create(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenClientRegistrationFoundAndNotAuthorizedClientThenEmpty() {
|
||||
when(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId)).thenReturn(Mono.just(this.clientRegistration));
|
||||
StepVerifier
|
||||
.create(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizedClientWhenClientRegistrationFoundThenFound() {
|
||||
when(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId)).thenReturn(Mono.just(this.clientRegistration));
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principalName, this.accessToken);
|
||||
Mono<OAuth2AuthorizedClient> saveAndLoad = this.authorizedClientService.saveAuthorizedClient(authorizedClient, this.principal)
|
||||
.then(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName));
|
||||
|
||||
StepVerifier.create(saveAndLoad)
|
||||
.expectNext(authorizedClient)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAuthorizedClientWhenAuthorizedClientNullThenIllegalArgumentException() {
|
||||
OAuth2AuthorizedClient authorizedClient = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.saveAuthorizedClient(authorizedClient, this.principal))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAuthorizedClientWhenPrincipalNullThenIllegalArgumentException() {
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principalName, this.accessToken);
|
||||
this.principal = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.saveAuthorizedClient(authorizedClient, this.principal))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenClientRegistrationIdNullThenIllegalArgumentException() {
|
||||
this.clientRegistrationId = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenClientRegistrationIdEmptyThenIllegalArgumentException() {
|
||||
this.clientRegistrationId = "";
|
||||
assertThatThrownBy(() -> this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenPrincipalNameNullThenIllegalArgumentException() {
|
||||
this.principalName = null;
|
||||
assertThatThrownBy(() -> this.authorizedClientService.removeAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenPrincipalNameEmptyThenIllegalArgumentException() {
|
||||
this.principalName = "";
|
||||
assertThatThrownBy(() -> this.authorizedClientService.removeAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenClientIdThenNoException() {
|
||||
when(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId)).thenReturn(Mono.empty());
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principalName, this.accessToken);
|
||||
Mono<Void> saveAndDeleteAndLoad = this.authorizedClientService.saveAuthorizedClient(authorizedClient, this.principal)
|
||||
.then(this.authorizedClientService.removeAuthorizedClient(this.clientRegistrationId, this.principalName));
|
||||
|
||||
StepVerifier.create(saveAndDeleteAndLoad)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizedClientWhenClientRegistrationFoundRemovedThenNotFound() {
|
||||
when(this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId)).thenReturn(Mono.just(this.clientRegistration));
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principalName, this.accessToken);
|
||||
Mono<OAuth2AuthorizedClient> saveAndDeleteAndLoad = this.authorizedClientService.saveAuthorizedClient(authorizedClient, this.principal)
|
||||
.then(this.authorizedClientService.removeAuthorizedClient(this.clientRegistrationId, this.principalName))
|
||||
.then(this.authorizedClientService.loadAuthorizedClient(this.clientRegistrationId, this.principalName));
|
||||
|
||||
StepVerifier.create(saveAndDeleteAndLoad)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user