如何在Spring中使用JDBC为ClientDetailsS​​erviceConfigurer添加客户端?

时间:2016-01-27 14:11:13

标签: java mysql spring-security-oauth2

我记忆中的事情如下:

@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");
}

我想使用JDBC实现。为此,我创建了以下表格(使用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
);

我是否需要在MySQL表中手动添加客户端?

我试过了:

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

希望Spring能在好的表中插入正确的东西,但似乎并没有这样做。为什么你可以在jdbc()之后进一步链接?

6 个答案:

答案 0 :(得分:9)

请休会这一步:

        
  1.         

    将此schema.sql放在资源文件夹中,以便在启动服务器后由SpringBoot检测到。如果你不使用spring boot,不用担心只从任何Mysql App客户端导入这个脚本(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.     
  3.         

    在OthorizationServer中注入DataSource,authenticationManager,UserDetailsS​​ervice

             @Autowired private MyUserDetailsService userDetailsService; @Inject private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource;     
  4.     
  5.         

    您需要创建这两个豆

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

    请不要忘记AuthorizationServer类顶部的@Configuration

        
  6.     
  7.         配置要在mysql数据库中创建的客户端应用程序:clients.jdbc(dataSource).withClient("clientapp") .authorizedGrantTypes("password", "refresh_token") .authorities("USER") .scopes("read", "write") .resourceIds(RESOURCE_ID) .secret("123456");         

    你已经完成了这件事。

        
  8.     
  9.         

    最重要的事情(我认为你忘了它..)是:使用AuthorizationServerEndpointsConfigurer配置你的端点:

            endpoints.userDetailsService(userDetailsService) .authorizationCodeServices(authorizationCodeServices()).authenticationManager(this.authenticationManager).tokenStore(tokenStore()).approvalStoreDisabled();     
  10. 那是男人,现在它应该有效;)

    随时可以要求更多......我很乐意提供帮助

    我已经发送了一条来自tweeter的消息!

答案 1 :(得分:8)

这个问题相当陈旧,但没有一个回复回答了提问者的原始问题。我对自己熟悉spring的oauth2实现时遇到了同样的问题,并且想知道为什么ClientDetailsServiceConfigurer不会持久存在通过JdbcClientDetailsServiceBuilder以编程方式添加的客户端(这是通过在配置器上调用jdbc(datasource)方法实例化,尽管网上的所有教程都显示了类似Wim发布的示例。在深入研究代码之后,我已经注意到了原因。嗯,这只是因为永远不会调用更新oauth_clients_details表的代码。配置所有客户端后,以下调用缺少的是:.and().build()。因此,Wim的代码实际上必须如下所示:

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

Et瞧,客户端clientapp现在已存在于数据库中。

答案 2 :(得分:2)

@AndroidLover的答案很好,但可以简化。除非需要jdbc令牌存储,否则您不需要创建oauth_access_token,oauth_refresh_token等表。

由于您只需要一个jdbc客户端详细信息服务,所以您需要做的就是:
1。创建客户端详细信息表 oauth_client_details ,例如:

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。例如,创建一个实现UserDetail接口的用户模型(我在这种情况下使用spring jpa,你可以使用mybatis,jdbc,等等):

@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。创建自定义用户详细信息服务。请注意,在您的实现中,您应该注入您的dao服务(在我的情况下,我注入了一个jpaRepository。)并且您的dao服务必须有一个方法来按用户名查找用户。:

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

@Autowired
UserRepository userRepository;

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

4。最后,配置您的身份验证服务器:

@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");
}
}

答案 3 :(得分:1)

@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); 
}

在这里,我正在检查数据库表oauth_client_details中是否存在任何客户端。如果存在任何客户端,我将删除该条目,因为它在第一次运行时不会出现任何错误,但是当您重新启动应用程序时,它会在数据库中添加条目时给出主键错误。那就是为什么我添加以下代码:

 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);

它将对您正常运行,没有任何错误。谢谢

答案 4 :(得分:1)

在关注@AndroidLover 回答时,对 Postgres 使用下表架构。

create table IF NOT EXISTS   oauth_client_details
(
    client_id               VARCHAR(256) PRIMARY KEY,
    resource_ids            VARCHAR(256),
    client_secret           VARCHAR(256),
    scope                   VARCHAR(256),
    authorized_grant_types  VARCHAR(256),
    web_server_redirect_uri VARCHAR(256),
    authorities             VARCHAR(256),
    access_token_validity   INTEGER,
    refresh_token_validity  INTEGER,
    additional_information  VARCHAR(4096),
    autoapprove             VARCHAR(256)
);

create table IF NOT EXISTS  oauth_client_token
(
    token_id          VARCHAR(256),
    token             bytea,
    authentication_id VARCHAR(256) PRIMARY KEY,
    user_name         VARCHAR(256),
    client_id         VARCHAR(256)
);

create table IF NOT EXISTS  oauth_access_token
(
    token_id          VARCHAR(256),
    token             bytea,
    authentication_id VARCHAR(256) PRIMARY KEY,
    user_name         VARCHAR(256),
    client_id         VARCHAR(256),
    authentication    bytea,
    refresh_token     VARCHAR(256)
);

create table IF NOT EXISTS  oauth_refresh_token
(
    token_id       VARCHAR(256),
    token          bytea,
    authentication bytea
);

create table IF NOT EXISTS  oauth_code
(
    code           VARCHAR(256),
    authentication bytea
);

create table IF NOT EXISTS  oauth_approvals
(
    userId         VARCHAR(256),
    clientId       VARCHAR(256),
    scope          VARCHAR(256),
    status         VARCHAR(10),
    expiresAt      TIMESTAMP,
    lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
create table IF NOT EXISTS  ClientDetails
(
    appId                  VARCHAR(256) PRIMARY KEY,
    resourceIds            VARCHAR(256),
    appSecret              VARCHAR(256),
    scope                  VARCHAR(256),
    grantTypes             VARCHAR(256),
    redirectUrl            VARCHAR(256),
    authorities            VARCHAR(256),
    access_token_validity  INTEGER,
    refresh_token_validity INTEGER,
    additionalInformation  VARCHAR(4096),
    autoApproveScopes      VARCHAR(256)
);

以下是我从数据库加载和验证令牌的 OAuth2 配置

@Configuration
@EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

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

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception {
        authorizationServerEndpointsConfigurer.tokenStore(tokenStore());
    }
}

答案 5 :(得分:0)

加上我的两分钱。

如果要在启动时初始化数据库结构(删除之前的内容),例如:

@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()
}

当心。

不必按某些特定顺序创建Bean,因此,当您在架构中的旧表中插入行,然后用新行替换行时,可能会遇到这种情况。因此,您可能会想一会儿,为什么仍然不插入行。我希望这会对某人有所帮助。