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(ClientDetailsServiceConfigurerclients) throws Exception{
clients.inMemory() // 使用in-memory存储.withClient("client") // client_id
.secret("secret") // client_secret
.authorizedGrantTypes("authorization_code") // 该client允许的授权类型
.scopes("app"); // 允许的授权范围
}
3. 授权流程访问授权页面:
localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http: // www.baidu.com
此时浏览器会让你输入用户名密码,这是因为 Spring Security 在默认情况下会对所有URL添加Basic Auth认证。默认的用户名为 user , 密码是随机生成的,在控制台日志中可以看到。
画风虽然很简陋,但是基本功能都具备了。点击 Authorize 后,浏览器就会重定向到百度,并带上 code 参数:
拿到 code 以后,就可以调用
POST/GET http://client:secret@localhost:8080/oauth/token来换取 access_token 了:
curl-XPOST-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 相关接口Spring Cloud Security OAuth2通过 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
在使用这套表结构之前要注意的是,对于MySQL来说,默认建表语句中主键是varchar(255)类型,在mysql中执行会报错,原因是mysql对varchar主键长度有限制。所以这里改成128即可。其次,语句中会有某些字段为 LONGVARBINARY 类型,它对应mysql的 blob 类型,也需要修改一下。
4.3 配置数据库建好后,下一步就是配置框架使用JDBC实现。方法还是编写 @Configuration 类继承 AuthorizationServerConfigurerAdapter :
@Autowired
private AuthenticationManagerauthenticationManager;
@Autowired
private DataSourcedataSource;
@Bean // 声明TokenStore实现public TokenStoretokenStore(){
return new JdbcTokenStore(dataSource);
}
@Bean // 声明ClientDetails实现public ClientDetailsServiceclientDetails(){
return new JdbcClientDetailsService(dataSource);
}
@Override // 配置框架应用上述实现public void configure(AuthorizationServerEndpointsConfigurerendpoints) throws Exception{
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
// 配置TokenServices参数DefaultTokenServicestokenServices= 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);
}
完成这些后,框架就会将中间产生的数据写到mysql中了。 oauth_client_details 是client表,可以直接在该表中添加记录来添加client:
4.4 需要注意的地方 这里不得不说 Spring 设计有一个奇葩地的方。注意看 oauth_access_token 表是存放访问令牌的,但是并没有直接在字段中存放token。Spring 使用 OAuth2AccessToken 来抽象与令牌有关的所有属性,在写入到数据库时, Spring将该对象通过JDK自带的序列化机制序列成字节 直接保存到了该表的 token 字段中。也就是说,如果只看数据表你是看不出 access_token 的值是多少,过期时间等信息的。这就给资源服务器的实现带来了麻烦。我们的资源提供方并没有使用Spring Security,也不想引入 Spring Security 的任何依赖,这时候就只能将 DefaultOAuth2AccessToken 的源码copy到资源提供方的项目中,然后读取 token 字段并反序列化还原对象来获取token信息。但是如果这样做还会遇到反序列化兼容性的问题,具体解决方法参考我另一篇博文: