在前面的一篇文章《Spring Cloud 安全:集成OAuth2实现身份认证和单点登录》中介绍了如何在Spring Cloud 微服务中集成OAuth2协议实现云的安全特性。在这个例子中使用的是内存数据库,如果你的系统是运行在生产环境中,并且拥有多个服务器实例时,很可能不希望使用内存来存储用户令牌。你或许需要在某个中心位置(具有一定程度的分布式一致性)来存储每个用户帐户的OAuth数据。最简单的方法是使用SQL数据库,这将是本文中讲述的例子。
首先,是为OAuth2设置数据库表,因此我们需要以下表格:
数据库表说明请参看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 (ClientDetailsServiceConfigurer 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);
}
}
至此,本例子就算完成了。用户认证时的令牌信息就都存储在数据库中,而且认证账号也是从表里读取。工程的完整代码下载地址