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. 小结