使用SPRING CLOUD SECURITY OAUTH2搭建授权服务
Spring Cloud Security OAuth2 是 Spring 对 OAuth2 的开源实现,优点是能与Spring Cloud技术线无缝集成,如果全部使用默认配置,开发者只需要添加注解就能完成 OAuth2 授权服务的搭建。
1. 添加依赖
授权服务是基于Spring Security的,因此需要在项目中引入两个依赖:
<
dependency
>
<
groupId
>org.springframework.cloud
</
groupId
>
<
artifactId
>spring-cloud-starter-security
</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.cloud
</
groupId
>
<
artifactId
>spring-cloud-starter-oauth2
</
artifactId
>
</
dependency
>
前者为 Security,后者为Security的OAuth2扩展。
2. 添加注解和配置
@EnableAuthorizationServer
注解:
@SpringBootApplication
@EnableAuthorizationServer
public
class AlanOAuthApplication {
public
static
void main(String[] args) {
SpringApplication.run(AlanOAuthApplication.
class, args);
}
}
完成这些我们的授权服务最基本的骨架就已经搭建完成了。但是要想跑通整个流程,我们必须分配 client_id
, client_secret
才行。Spring Security OAuth2的配置方法是编写@Configuration
类继承AuthorizationServerConfigurerAdapter
,然后重写void configure(ClientDetailsServiceConfigurer clients)
方法,如:
@Override
public
void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
//
使用in-memory存储
.withClient("client")
//
client_id
.secret("secret")
//
client_secret
.authorizedGrantTypes("authorization_code")
//
该client允许的授权类型
.scopes("app");
//
允许的授权范围
3. 授权流程
访问授权页面:
// www.baidu.com
user
, 密码是随机生成的,在控制台日志中可以看到。
Authorize
后,浏览器就会重定向到百度,并带上code
参数:
code
以后,就可以调用
<code livecodeserver="" has-numbering"="" style="box-sizing: border-box; font-family: "Source Code Pro", monospace; font-size: inherit; padding: 0px; color: inherit; white-space: pre; border-radius: 0px; margin: 0px; border: 0px; outline: 0px; vertical-align: baseline; display: block; word-break: break-word; word-wrap: normal; background: transparent;">POST/GET http://client:secret@localhost:8080/oauth/token
- 1
access_token
了:
curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"
返回如下:
{
"access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556",
"token_type": "bearer",
"expires_in": 2591999,
"scope": "app"
}
到此我们最最基本的授权服务就搭建完成了。然而,这仅仅是个demo,如果要在生产环境中使用,还需要做更多的工作。
4. 使用MYSQL存储ACCESS_TOKEN和CLIENT信息
把授权服务器中的数据存储到数据库中并不难,因为 Spring Cloud Security OAuth 已经为我们设计好了一套Schema和对应的DAO对象。但在使用之前,我们需要先对相关的类有一定的了解。
4.1 相关接口
DefaultTokenServices
类来完成token生成、过期等 OAuth2 标准规定的业务逻辑,而DefaultTokenServices
又是通过TokenStore
接口完成对生成数据的持久化。在上面的demo中,TokenStore
的默认实现为InMemoryTokenStore
,即内存存储。 对于Client信息,ClientDetailsService
接口负责从存储仓库中读取数据,在上面的demo中默认使用的也是InMemoryClientDetialsService
实现类。说到这里就能看出,要想使用数据库存储,只需要提供这些接口的实现类即可。庆幸的是,框架已经为我们写好JDBC实现了,即JdbcTokenStore
和JdbcClientDetailsService
。
4.2 建表
要想使用这些JDBC实现,首先要建表。框架为我们提前设计好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
LONGVARBINARY
类型,它对应mysql的blob
类型,也需要修改一下。
4.3 配置
@Configuration
类继承AuthorizationServerConfigurerAdapter
:
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
//
声明TokenStore实现
public TokenStore tokenStore() {
return
new JdbcTokenStore(dataSource);
}
@Bean
//
声明 ClientDetails实现
public ClientDetailsService clientDetails() {
return
new JdbcClientDetailsService(dataSource);
}
@Override
//
配置框架应用上述实现
public
void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
//
配置TokenServices参数
DefaultTokenServices tokenServices =
new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(
false);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds( (
int) TimeUnit.DAYS.toSeconds(30));
//
30天
endpoints.tokenServices(tokenServices);
}
oauth_client_details
是client表,可以直接在该表中添加记录来添加client:
4.4 需要注意的地方
oauth_access_token
表是存放访问令牌的,但是并没有直接在字段中存放token。Spring 使用OAuth2AccessToken
来抽象与令牌有关的所有属性,在写入到数据库时,Spring将该对象通过JDK自带的序列化机制序列成字节直接保存到了该表的token
字段中。也就是说,如果只看数据表你是看不出access_token
的值是多少,过期时间等信息的。这就给资源服务器的实现带来了麻烦。我们的资源提供方并没有使用Spring Security,也不想引入 Spring Security 的任何依赖,这时候就只能将 DefaultOAuth2AccessToken
的源码copy到资源提供方的项目中,然后读取token
字段并反序列化还原对象来获取token信息。但是如果这样做还会遇到反序列化兼容性的问题,具体解决方法参考我另一篇博文:
5. 总结
JdbcTokenStore
或ClientDetailsService
来实适应业务需要,甚至可以直接从0开始实现接口,完全不用框架提供的实现。另外,Spring 直接将DefaultOAuth2AccessToken
序列化成字节保存到数据库中的设计,我认为是非常不合理的。或许设计者的初衷是保密access_token
,但是通过加密的方法也可以实现,完全不应该直接扔字节。不过通过定制TokenStore
接口,我们可以使用自己的表结构而不拘泥于默认实现。