OAuth 2.0定义了四种授权方式:
- 授权码模式(authorization code):功能最完整、流程最严密的授权模式。特点是通过第三方应用的后台服务器,与服务提供平台的认证服务器进行互动获取资源。
- 简化模式(implicit):不通过第三方应用服务器,直接在浏览器中向认证服务器申请token令牌,跳过了授权码这个步骤。所有步骤在浏览器中完成,token对用户可见,且第三方应用不需要认证。
- 密码模式(resource owner password credentials):用户向第三方应用提供自己的用户名和密码。第三方应用使用这些信息,向服务提供平台索要授权。在这种模式中,用户必须把自己的密码给第三方应用,但是第三方应用不得储存密码。这通常用在用户对第三方应用高度信任的情况下,比如第三方应用是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
- 客户端模式(client credentials):指第三方应用以自己的名义,而不是以用户的名义,向服务提供平台进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向第三方应用注册,第三方应用以自己的名义要求服务提供平台提供服务,其实不存在授权问题。
这里详细介绍客户端模式,通过client_id和client_secret获取access_token。
为了支持动态客户端注册,我们会把client_id保存在数据库中,而不是写死在配置文件中。
本文需要读者对spring security有一定的了解。
1. maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
注意这里我们引用了spring-jdbc, 因为我们会把 client_id 和 client_secret保存到数据库中。
2. OAuth2.0服务器配置
应用入口:
@SpringBootApplication
@EnableAuthorizationServer
public class AuthCenterApplication {
/**
* 入口
* @param args 参数
*/
public static void main(String[] args) {
SpringApplication.run(AuthCenterApplication.class, args);
}
}
配置类:
public class OAuth2AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource()).passwordEncoder(passwordEncoder());
}
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
...
}
我们为应用程序配置了数据源。
3. 数据表
下面是保存我们client信息的表
create table 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)
);
字段说明:
• client_id – 客户端id
• client_secret – 客户端密码
• access_token_validity – access_token是否还有效
• authorities – 角色
• scope – 允许的操作列表
• authorized_grant_types - 客户端允许的授权方式
注意,一个客户端可能对应多个用户
4. 插入客户端数据
INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
("fooClientIdPassword", "secret", "foo,read,write",
"password,authorization_code,refresh_token", null, null, 36000, 36000, null, true);
字段说明请看上节。
5. 单元测试
@Test
public void givenDBUser_whenRevokeToken_thenAuthorized() {
String accessToken = obtainAccessToken("fooClientIdPassword", "john123");
assertNotNull(accessToken);
}
private String obtainAccessToken(String clientId, String clientSecret) {
Map<String, String> params = new HashMap<String, String>();
params.put("grant_type", "client_credentials");
params.put("client_id", clientId);
params.put("client_secret", clientSecret);
MvcResult result = mockMvc.perform(post("/oauth/token")
.contentType(MediaType.APPLICATION_JSON)
.params(param)
.andReturn();
Gson gson = new Gson();
Map map = gson.fromJson(result.getResponse().getContentAsString(), Map.class);
return map.get("access_token")
}
注意,数据库表oauth_client_details中需要有对应的客户端数据。
6. 小结