web-dev-qa-db-fra.com

Comment ajouter un client à l'aide de JDBC pour ClientDetailsServiceConfigurer au printemps?

J'ai la chose en mémoire qui fonctionne comme suit:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
               .withClient("clientapp")
               .authorizedGrantTypes("password", "refresh_token")
               .authorities("USER")
               .scopes("read", "write")
               .resourceIds(RESOURCE_ID)
               .secret("123456");
}

Je voudrais utiliser l'implémentation JDBC. Pour cela, j'ai créé les tableaux suivants (en utilisant MySQL):

-- Tables for OAuth token store

CREATE TABLE oauth_client_details (
  client_id               VARCHAR(255) PRIMARY KEY,
  resource_ids            VARCHAR(255),
  client_secret           VARCHAR(255),
  scope                   VARCHAR(255),
  authorized_grant_types  VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities             VARCHAR(255),
  access_token_validity   INTEGER,
  refresh_token_validity  INTEGER,
  additional_information  VARCHAR(4096),
  autoapprove             TINYINT
);

CREATE TABLE oauth_client_token (
  token_id          VARCHAR(255),
  token             BLOB,
  authentication_id VARCHAR(255),
  user_name         VARCHAR(255),
  client_id         VARCHAR(255)
);

CREATE TABLE oauth_access_token (
  token_id          VARCHAR(255),
  token             BLOB,
  authentication_id VARCHAR(255),
  user_name         VARCHAR(255),
  client_id         VARCHAR(255),
  authentication    BLOB,
  refresh_token     VARCHAR(255)
);

CREATE TABLE oauth_refresh_token (
  token_id       VARCHAR(255),
  token          BLOB,
  authentication BLOB
);

CREATE TABLE oauth_code (
  code           VARCHAR(255),
  authentication BLOB
);

Dois-je ajouter manuellement un client dans les tables MySQL?

J'ai essayé ceci:

clients.jdbc(dataSource).withClient("clientapp")
               .authorizedGrantTypes("password", "refresh_token")
               .authorities("USER")
               .scopes("read", "write")
               .resourceIds(RESOURCE_ID)
               .secret("123456");

En espérant que Spring insèrerait les bonnes choses dans les bonnes tables, mais cela ne semble pas le faire. Pourquoi est-il possible de chaîner davantage après jdbc()?

14
Wim Deblauwe

Cette question est assez ancienne mais aucune des réponses n'a donné de réponse au problème initial de l'interrogateur. Je suis tombé sur le même problème en me familiarisant avec l'implémentation oauth2 de Spring et je me suis demandé pourquoi le ClientDetailsServiceConfigurer ne persistait pas les clients qui étaient ajoutés par programme via le JdbcClientDetailsServiceBuilder (qui est instancié en appelant le jdbc(datasource) méthode sur le configurateur), malgré le fait que tous les tutoriels sur le net montraient un exemple similaire comme celui publié par Wim. Après avoir approfondi le code, j'ai remarqué la raison. Eh bien, c'est simplement parce que le code pour mettre à jour la table oauth_clients_details N'est jamais appelé. Ce qui manque, c'est l'appel suivant après avoir configuré tous les clients: .and().build(). Ainsi, le code de Wim doit en fait ressembler à ceci:

clients.jdbc(dataSource).withClient("clientapp")
           .authorizedGrantTypes("password", "refresh_token")
           .authorities("USER")
           .scopes("read", "write")
           .resourceIds(RESOURCE_ID)
           .secret("123456").and().build();

Et voila, le client clientapp est maintenant conservé dans la base de données.

10
user35934

Veuillez suivre ces étapes:

  1. placez ce schéma.sql dans votre dossier de ressources pour être détecté par SpringBoot une fois que vous démarrez votre serveur. Si vous n'utilisez pas Spring Boot, ne vous inquiétez pas, importez simplement ce script depuis n'importe quel client d'application Mysql (phpmyadmin, HeidiSQL, Navicat ..)

    drop table if exists oauth_client_details; create table oauth_client_details ( client_id VARCHAR(255) PRIMARY KEY, resource_ids VARCHAR(255), client_secret VARCHAR(255), scope VARCHAR(255), authorized_grant_types VARCHAR(255), web_server_redirect_uri VARCHAR(255), authorities VARCHAR(255), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(255) ); drop table if exists oauth_client_token; create table oauth_client_token ( token_id VARCHAR(255), token LONG VARBINARY, authentication_id VARCHAR(255) PRIMARY KEY, user_name VARCHAR(255), client_id VARCHAR(255) ); drop table if exists oauth_access_token; create table oauth_access_token ( token_id VARCHAR(255), token LONG VARBINARY, authentication_id VARCHAR(255) PRIMARY KEY, user_name VARCHAR(255), client_id VARCHAR(255), authentication LONG VARBINARY, refresh_token VARCHAR(255) ); drop table if exists oauth_refresh_token; create table oauth_refresh_token ( token_id VARCHAR(255), token LONG VARBINARY, authentication LONG VARBINARY ); drop table if exists oauth_code; create table oauth_code ( code VARCHAR(255), authentication LONG VARBINARY ); drop table if exists oauth_approvals; create table oauth_approvals ( userId VARCHAR(255), clientId VARCHAR(255), scope VARCHAR(255), status VARCHAR(10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP ); drop table if exists ClientDetails; create table ClientDetails ( appId VARCHAR(255) PRIMARY KEY, resourceIds VARCHAR(255), appSecret VARCHAR(255), scope VARCHAR(255), grantTypes VARCHAR(255), redirectUrl VARCHAR(255), authorities VARCHAR(255), access_token_validity INTEGER, refresh_token_validity INTEGER, additionalInformation VARCHAR(4096), autoApproveScopes VARCHAR(255) );
  2. Injectez votre DataSource, authenticationManager, UserDetailsService dans votre OthorizationServer

    @Autowired private MyUserDetailsService userDetailsService; @Inject private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource;
  3. Vous devrez créer ces deux beans

    @Bean public JdbcTokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean protected AuthorizationCodeServices authorizationCodeServices() { return new JdbcAuthorizationCodeServices(dataSource); }

    et n'oubliez pas la @Configuration au-dessus de votre classe AuthorizationServer

  4. Configurez vos applications clientes à créer dans votre base de données mysql: clients.jdbc(dataSource).withClient("clientapp") .authorizedGrantTypes("password", "refresh_token") .authorities("USER") .scopes("read", "write") .resourceIds(RESOURCE_ID) .secret("123456");

    vous l'avez déjà fait.

  5. la chose la plus importante (et je pense que vous l'avez oublié ..) est: de configurer vos points de terminaison avec AuthorizationServerEndpointsConfigurer:

    endpoints.userDetailsService(userDetailsService) .authorizationCodeServices(authorizationCodeServices()).authenticationManager(this.authenticationManager).tokenStore(tokenStore()).approvalStoreDisabled();

et c'est ça mec, maintenant ça devrait marcher;)

Et n'hésitez pas à en demander plus ... Je serai ravi de vous aider

Je vous ai envoyé un message de Tweeter!

11
AndroidLover

La réponse de @AndroidLover est bonne, mais elle pourrait être simplifiée. Vous n'avez pas besoin de créer des tables comme oauth_access_token, oauth_refresh_token, etc. sauf si vous avez besoin d'un magasin de jetons jdbc.

Comme vous n'avez besoin que d'un service de détail client jdbc, il vous suffit de:
1. créer une table de détail client oauth_client_details, par exemple:

drop table if exists oauth_client_details;
    create table oauth_client_details (
    client_id VARCHAR(255) PRIMARY KEY,
    resource_ids VARCHAR(255),
    client_secret VARCHAR(255),
    scope VARCHAR(255),
    authorized_grant_types VARCHAR(255),
    web_server_redirect_uri VARCHAR(255),
    authorities VARCHAR(255),
    access_token_validity INTEGER,
    refresh_token_validity INTEGER,
    additional_information VARCHAR(4096),
    autoapprove VARCHAR(255)
    );

2. créez un modèle utilisateur qui implémente l'interface UserDetail, par exemple (j'utilise spring jpa dans ce cas, vous pouvez utiliser mybatis, jdbc, peu importe):

@Entity
@Table(name = "users")
public class User implements UserDetails {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_id", nullable = false, updatable = false)
private String id;

@Column(name = "username", nullable = false, unique = true)
private String username;

@Column(name = "password", nullable = false)
private String password;

@Column(name = "enabled", nullable = false)
@Type(type = "org.hibernate.type.NumericBooleanType")
private boolean enabled;

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public void setUsername(String username) {
    this.username = username;
}

public void setPassword(String password) {
    this.password = password;
}

public void setEnabled(boolean enabled) {
    this.enabled = enabled;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    authorities.add((GrantedAuthority) () -> "ROLE_USER");
    return authorities;
}

@Override
public String getPassword() {
    return this.password;
}

@Override
public String getUsername() {
    return this.username;
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

3. créez un service de détail d'utilisateur personnalisé. notez que dans votre implémentation, vous devez injecter votre service dao (dans mon cas, j'ai injecté un jpaRepository.) et votre service dao [[# # ~] doit [~ # ~] avoir une méthode pour trouver utilisateur par nom d'utilisateur:

@Service("userDetailsService")
public class UserService implements UserDetailsService {

@Autowired
UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String userName) throws 
UsernameNotFoundException {
    return userRepository.findByUsername(userName);
}
}

4. enfin, configurez votre serveur d'authentification:

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
@Qualifier("dataSource")
DataSource dataSource;

@Autowired
@Qualifier("userDetailsService")
private UserDetailsService userDetailsService;


@Autowired
private AuthenticationManager authenticationManager;

@Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) {
    configurer
            .authenticationManager(authenticationManager)                
            .approvalStoreDisabled()
            .userDetailsService(userDetailsService);
}


@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
{
    clients
            .jdbc(dataSource)
            .inMemory().withClient("my-trusted-
client").secret("secret").accessTokenValiditySeconds(3600)
            .scopes("read", "write").authorizedGrantTypes("password", 
"refresh_token").resourceIds("resource");
}
}
2
haozhechen
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

    JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);

    if(!jdbcClientDetailsService.listClientDetails().isEmpty() ) {          
    jdbcClientDetailsService.removeClientDetails(CLIEN_ID);     
    }

    if(jdbcClientDetailsService.listClientDetails().isEmpty() ) {
        configurer.jdbc(dataSource).withClient(CLIEN_ID).secret(encoder.encode(CLIENT_SECRET))
        .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT)
        .scopes(SCOPE_READ, SCOPE_WRITE, TRUST).accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
        .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS).and().build();                
    }       
    configurer.jdbc(dataSource).build().loadClientByClientId(CLIEN_ID); 
}

Ici, je vérifie qu'il existe un client dans la table de base de données oauth_client_details. S'il existe un client, je supprime cette entrée car la première fois, cela fonctionnera sans erreur, mais lorsque vous redémarrez votre application, elle donne des erreurs de clé primaire lors de l'ajout d'une entrée dans la base de données. C'est pourquoi j'ai ajouté ce code:

 if(!jdbcClientDetailsService.listClientDetails().isEmpty() ) { 

    jdbcClientDetailsService.removeClientDetails(CLIEN_ID);

    }

Après avoir supprimé l'entrée client, vous devez ajouter un client. Voici le code pour ajouter un client:

if(jdbcClientDetailsService.listClientDetails().isEmpty() ) {
        configurer.jdbc(dataSource).withClient(CLIEN_ID).secret(encoder.encode(CLIENT_SECRET))
        .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT)
        .scopes(SCOPE_READ, SCOPE_WRITE, TRUST).accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
        .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS).and().build();

    } 

Dans ce code, vous pouvez modifier la configuration comme vous le souhaitez, car nous supprimons l'entrée client à chaque fois après le redémarrage de votre application.

ici, nous chargeons tous les détails du client:

configurer.jdbc(dataSource).build().loadClientByClientId(CLIEN_ID);

Cela fonctionnera bien pour vous sans aucune erreur. Merci

1
swapnil kadam

Ajout de mes deux cents.

Si vous initialisez des structures db au démarrage (avec suppression précédente), par exemple comme ceci:

@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
    //...setting dataSource and databasePopulator
}
private DatabasePopulator databasePopulator() {
    //...adding your schema script
}
@Bean
public DataSource dataSource() {
    //...setting driverclassname, url, etc
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //...
    clients.jdbc(this.dataSource()).withClient("example").(...).build()
}

il faut se méfier.

Les beans ne doivent pas être créés dans un ordre spécifique, vous pouvez donc intercepter une situation lorsque vous insérez des lignes dans vos anciennes tables, puis les remplacez par de nouvelles, à partir de votre schéma. Donc, vous vous demandez peut-être pendant un certain temps pourquoi il n'insère toujours pas de lignes. J'espère que cela aiderait quelqu'un.

0
user2501323