Já discutimos Como configurar uma autenticação OAuth 2.0 e como criar um armazenamento de token personalizado. No último artigo desta série, você aprenderá a implementar um registro de cliente dinâmico personalizado usando segurança de primavera-oauth2. Eu recomendo que você leia Parte 1 e Parte 2 primeiro, pois vamos continuar de onde paramos.
Vamos começar criando a entidade responsável por armazenar os dados do 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<>(); } |
Aqui está o respectivo repositório:
|
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); } |
Agora, podemos implementar o ClientDetailsService da classe de segurança do 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; } } |
Observe que estou usando o BaseClientDetails em vez de implementar a classe ClientDetails interface. Essa parece ser a melhor opção, pois até mesmo a implementação padrão do JDBC a utiliza.
Por fim, precisamos alterar nosso AuthorizationServerConfig para usar nosso 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); } } |
Esta é a aparência da classe inteira:
|
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()"); } } |
Agora você pode simplesmente inserir um novo cliente em seu banco de dados e usar essas credenciais para autenticar via 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 - O truque é implementar o org.springframework.security.oauth2.provider.ClientDetailsService e passá-la como um parâmetro para sua interface ClientDetailsServiceConfigurer:
|
1 2 3 4 |
@Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer.withClientDetails(couchbaseClientDetailsService); } |
Se você tiver alguma dúvida, envie-me um tweet para @deniswsrosa