Ya hemos hablado de cómo configurar una autenticación OAuth 2.0 y cómo crear un almacén de fichas personalizado. En el último artículo de esta serie, aprenderá a implementar un registro de cliente dinámico personalizado utilizando spring-seguridad-oauth2. Le recomiendo que lea Parte 1 y Parte 2 primero, ya que vamos a continuar desde donde lo hemos dejado.
Empecemos por crear la entidad responsable de almacenar los datos del cliente:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import com.couchbase.client.java.repository.annotation.Id; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.couchbase.core.mapping.Document; import javax.validation.constraints.NotNull; import java.util.*; @NoArgsConstructor @Data @Document public class CustomClientDetails extends BasicEntity { @Id @NotNull private String id; @NotNull private String clientId; private String clientSecret; private Set<String> resourceIds = new HashSet<>(); private boolean secretRequired; private boolean scoped; private Set<String> scope = new HashSet<>(); private Set<String> authorizedGrantTypes = new HashSet<>(); private Set<String> registeredRedirectUri = new HashSet<>(); private Collection<String> authorities = new HashSet<>(); private Integer accessTokenValiditySeconds; private Integer refreshTokenValiditySeconds; private boolean autoApprove; private Map<String, Object> additionalInformation = new HashMap<>(); } |
Aquí está el repositorio correspondiente:
|
1 2 3 4 5 6 7 8 9 10 11 |
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; import org.springframework.data.couchbase.core.query.ViewIndexed; import org.springframework.data.couchbase.repository.CouchbasePagingAndSortingRepository; @N1qlPrimaryIndexed @ViewIndexed(designDoc = "customClientDetails") public interface CustomClientDetailsRepository extends CouchbasePagingAndSortingRepository<CustomClientDetails, String> { CustomClientDetails findByClientId(String clientId); } |
Ahora, podemos implementar el ServicioDeDetallesDelCliente de la clase de seguridad de Spring:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.stereotype.Service; import java.util.stream.Collectors; @Service public class CouchbaseClientDetailsService implements ClientDetailsService { @Autowired private CustomClientDetailsRepository customClientDetailsRepository; @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { CustomClientDetails client = customClientDetailsRepository.findByClientId(clientId); String resourceIds = client.getResourceIds().stream().collect(Collectors.joining(",")); String scopes = client.getScope().stream().collect(Collectors.joining(",")); String grantTypes = client.getAuthorizedGrantTypes().stream().collect(Collectors.joining(",")); String authorities = client.getAuthorities().stream().collect(Collectors.joining(",")); BaseClientDetails base = new BaseClientDetails(client.getClientId(), resourceIds, scopes, grantTypes, authorities); base.setClientSecret(client.getClientSecret()); base.setAccessTokenValiditySeconds(client.getAccessTokenValiditySeconds()); base.setRefreshTokenValiditySeconds(client.getRefreshTokenValiditySeconds()); base.setAdditionalInformation(client.getAdditionalInformation()); base.setAutoApproveScopes(client.getScope()); return base; } } |
Tenga en cuenta que estoy utilizando el BaseClientDetails en lugar de implementar la clase DetallesDelCliente interfaz. Parece ser la mejor opción, ya que incluso la implementación estándar de JDBC la utiliza.
Por último, tenemos que cambiar nuestro AuthorizationServerConfig utilizar nuestro CouchbaseClientDetailsService:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private CouchbaseClientDetailsService couchbaseClientDetailsService; @Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer.withClientDetails(couchbaseClientDetailsService); } } |
Este es el aspecto de toda la clase:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Autowired private AuthenticationManager authenticationManager; @Autowired private CouchbaseClientDetailsService couchbaseClientDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer.withClientDetails(couchbaseClientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .authenticationManager(authenticationManager); } @Override public void configure( AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } } |
Ahora puede simplemente insertar un nuevo cliente en su base de datos y utilizar estas credenciales para autenticarse a través de OAuth:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static final String GRANT_TYPE_PASSWORD = "password"; static final String AUTHORIZATION_CODE = "authorization_code"; static final String REFRESH_TOKEN = "refresh_token"; static final String IMPLICIT = "implicit"; static final String SCOPE_READ = "read"; static final String SCOPE_WRITE = "write"; static final String TRUST = "trust"; private void createOauthClients() { CustomClientDetails client = new CustomClientDetails(); client.setId("someId"); client.setResourceIds(new HashSet<>(Arrays.asList("resource_id")) ); client.setClientId("android-client"); client.setClientSecret("android-secret"); client.setAuthorizedGrantTypes(new HashSet<>(Arrays.asList(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT))); client.setScope(new HashSet<>(Arrays.asList(SCOPE_READ, SCOPE_WRITE, TRUST))); client.setSecretRequired(true); client.setAccessTokenValiditySeconds(50000); client.setRefreshTokenValiditySeconds(50000); client.setScoped(false); customClientDetailsRepository.save(client); } |
TL;DR - El truco está en aplicar el org.springframework.security.oauth2.provider.ClientDetailsService y pasarlo como parámetro a su ConfiguradorDeDetallesDelCliente:
|
1 2 3 4 |
@Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer.withClientDetails(couchbaseClientDetailsService); } |
Si tienes alguna pregunta, envíame un tweet a @deniswsrosa