Spring Cloud 安全:集成OAuth2的数据库方式实现

在前面的一篇文章《Spring Cloud 安全:集成OAuth2实现身份认证和单点登录》中介绍了如何在Spring Cloud 微服务中集成OAuth2协议实现云的安全特性。在这个例子中使用的是内存数据库,如果你的系统是运行在生产环境中,并且拥有多个服务器实例时,很可能不希望使用内存来存储用户令牌。你或许需要在某个中心位置(具有一定程度的分布式一致性)来存储每个用户帐户的OAuth数据。最简单的方法是使用SQL数据库,这将是本文中讲述的例子。

首先,是为OAuth2设置数据库表,因此我们需要以下表格:

  • oauth_client_details
  • oauth_client_token
  • oauth_access_token
  • oauth_refresh_token
  • oauth_code
  • oauth_approvals
  • ClientDetails

数据库表说明请参看http://andaily.com/spring-oauth-server/db_table_description.html
除此之外创建一个表account存放认证用户的用户名和密码。

当我们使用Spring Boot时,我们可以创建一个名为schema.sql的文件,此文件存放在工程中的资源文件夹里。在启动时,Spring Boot将检测该文件,并将在我们指定的数据库里运行它。

全部设置完数据库schema后,我们需要填充oauth_client_details表和account表。同理, 我们只需要创建一个名为data.sql的文件。同schema.sql一样,Spring Boot在启动时会选择文件并在我们的数据库中运行。此时,我们已准备好与SQL数据库相关的所有内容。

现在到编码了。创建一个新的认证服务,在pom中引入jpa和mysql驱动:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
dependency>

在配置文件application.properties中添加数据库配置

spring.datasource.url = jdbc:mysql://localhost:3306/oauth_example?createDatabaseIfNotExist=true
spring.database.driverClassName = com.mysql.jdbc.Driver
spring.jpa.database = MySQL
spring.datasource.platform = mysql
。。。。。。

我们创建一个配置类AppConfig.class,在这里配置DataStore和TokenStore。

@Configuration
public class AppConfig {
    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${spring.database.driverClassName}")
    private String dbDriverClassName;

    @Value("${spring.datasource.username}")
    private String dbUsername;

    @Value("${spring.datasource.password}")
    private String dbPassword;

    @Bean
    public DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(dbDriverClassName);
        dataSource.setUrl(datasourceUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);

        return dataSource;
    }

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

如上所述,我们的 TokenStore 由扩展 TokenStore的JdbcTokenStore定义 。我们还将DataSource传递给 JdbcTokenStore,因此我们让应用程序知道我们正在使用指定的 DataSource 存储我们所有的OAuth2数据。另一方面,DataSource指定我们使用的SQL数据库。

但这还不够。现在我们需要连接一切,数据库 - 认证服务器 - Spring Boot应用程序。认证服务器将成为这里的桥梁。所以,让我们开始吧。我们创建了一个类( AuthServerOAuth2Config)来扩展 AuthorizationServerConfigurerAdapter 。然后我们需要覆盖 configure (ClientDetailsS​​erviceConfigurer clients)和configure (AuthorizationServerEndpointsConfigurer endpoints)方法来连接所有内容。

@Configuration
@EnableAuthorizationServer
@Order(6)
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
    ......
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(appConfig.dataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(jwtAccessTokenConverter()).userDetailsService(userDetailsService);
        endpoints
         .authenticationManager(authenticationManager)
         .tokenStore(appConfig.tokenStore()); // Persist the tokens in the database
    }
    ......
}

现在我们已经实现了带有JDBC的OAuth2。我们可以通过/ oauth / token向我们的服务器发送POST请求来生成一些令牌,并且正确设置了Authorization类型,标题和正文(表单数据)。

由于用户账号存储在数据库中,我们需要通过在数据库中查询然后来使用。我们在定义用户的时候需要实现UserDetails接口,这样我们的用户实体即为Spring Security所使用的用户。这里我们需要重写UserDetailsService接口,然后实现该接口中的loadUserByUsername方法,通过该方法查询到对应的用户,这里之所以要实现UserDetailsService接口,是因为在Spring Security中我们配置相关参数需要UserDetailsService类型的数据。

@Service
public class AccountUserDetailsService implements UserDetailsService {
    private AccountRepository accountRepository;

    @Autowired
    public AccountUserDetailsService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return accountRepository
                .findByUsername(username)
                .map(account -> new User(account.getUsername(), account.getPassword(), AuthorityUtils.createAuthorityList("ROLE_USER")))
                .orElseThrow(() -> new UsernameNotFoundException("Could not find " + username));
    }
}

配置Spring Security,引入自定义UserDetailsService

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    ......

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceBean());
    }

    @Override
    @Bean(name = "userDetailsService")
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new AccountUserDetailsService(accountRepository);
    }

}

至此,本例子就算完成了。用户认证时的令牌信息就都存储在数据库中,而且认证账号也是从表里读取。工程的完整代码下载地址

你可能感兴趣的:(微服务)