# OAuth2 测试

若同时引用了fly-security依赖,可以进行OAuth2相关的单元测试:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-test</artifactId>
    <version>${fly.version}</version>
</dependency>

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-security</artifactId>
    <version>${fly.version}</version>
</dependency>

在编写代码前,先了解下fly-test中针对OAuth2的单元测试配置。

# OAuth2 配置

单元测试中OAuth2的相关配置,可区分于正式运行所用的OAuth2

配置前缀:fly.test.security.oauth2

详细见配置类:fly.test.security.oauth2.TOAuth2Properties

配置 说明 类型 默认值
server-url 测试OAuth2服务地址 String -
login-uri 登录地址 String /login
authorization-uri 认证地址 String /oauth2/authorize
token-uri 令牌地址 String /oauth2/token
user-info-uri 获取用户信息地址 String /oauth2/userinfo
introspection-uri 内省地址 String /oauth2/introspect
end-session-uri 登出地址 String /oauth2/logout
default-redirect-uri 默认跳转地址 String http://app.example.com/authroized

application.yml

fly:
  test:
    security:
      oauth2:
        server-url: "${oauth2.test.server-url:http://localhost:9000}"
        login-uri: "${oauth2.test.login-uri:/login}"
        authorization-uri: "${oauth2.test.authorization-uri:/oauth2/authorize}"
        token-uri: "${oauth2.test.token-uri:/oauth2/token}"
        user-info-uri: "${oauth2.test.user-info-uri:/oauth2/userinfo}"
        introspection-uri: "${oauth2.test.introspection-uri:/oauth2/introspect}"
        end-session-uri: "${oauth2.test.end-session-uri:/oauth2/logout}"
        default-redirect-uri: "${oauth2.test.default-redirect-uri:http://app.example.com/authroized}"

需要注意的是,TEST模块有提供默认配置/fly/spring-defaults.yml(加载机制可见配置加载),引入fly-test依赖即生效:

fly:
  test:
    security:
      oauth2:
        server-url: "${oauth2.server-url:${fly.security.oauth2.server-url:}}"

一般我们会通过oauth2.server-urlfly.security.oauth2.server-url配置OAuth2,默认从这两处取值作为单元测试的OAuth2配置,如需要可在当前工程配置覆盖。

# 安全配置

具体配置类:fly.test.security.TestSecurityProperties

application.yml

fly:
  test:
    security:
      users:  # 测试用户
        user1: password1  # 简化配置,只有 username 和 password
        user2:
          username: user2
          password: password2
          scopes: [ read, write ]  # 该用户拥有的 scope 权限
      clients:  # 测试客户端
        client1: secret1  # 简化配置,只有客户端 ID 和密钥
        client2:
          client-id: client2
          client-secret: secret2
          redirect-uri: 
          logout-uri: 
  • 测试用户及客户端

配置后,可以在后续的OAuth2Authorization操作中作为参数传入使用,若不配置也可以在单元测试中自行创建TUser/TClient对象,主要便于在多个单元测试中重复使用的用户或客户端统一管理,配置后一般在同名方法中传入username或者clientId即可。

# OAuth2 请求

接口TOAuth2Template类似于Rest请求的TRestTemplate,主要用于OAuth2的单元测试,又衍生出针对客户端、用户信息、认证等不同场景划分的操作对象。可以通过注入获取:

import fly.test.security.oauth2.TOAuth2Template;

...

@Autowired
protected TOAuth2Template oauth2Template;

下面将罗列关键方法:

  • 获取当前请求的OAuth2 配置oauth2Template.getProperties()
  • 从单元测试安全配置里获取客户端或者用户,若不存在则抛异常:

mustGetClient(String clientId)

mustGetUser(String username)

以上述等获取对应功能操作对象方法为基础,TOAuth2Template包装了大量快捷测试的方法,如:

@Test
public void getUserInfo() {
    final THttpTemplate http = new THttpTemplate();
    
    final TClient client = new TClient("client", "secret", "http://example.com");
    final TUser user = new TUser("user", "password");
    oauth2.authorizeForUserInfo(http, client, user);
}

更多内容可以查看接口注解。

# 授权操作

TOAuth2AuthorizationOperations接口提供OAuth2授权端点操作。一般通过TOAuth2Template或者TOAuth2Client等接口的authorization方法获取对象,示例:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testAuthorize() {
    // 方式1
    TClient client1 = oauth2Template.mustGetClient("client1");
    TOAuth2AuthorizationOperations authorizationOps1 = oauth2Template.authorization(client1);
    
    // 方式2
    TOAuth2Client clientOps = oauth2Template.client("client1");
    TOAuth2AuthorizationOperations authorizationOps2 = clientOps.authorization();
    
    // ...
}

示例中的client1均是从安全配置的测试客户端列表中获取,后续的授权都基于该客户端身份。常用方法有:

  • authorize授权

authorize前缀命名的方法,都是通过授权,然后返回不同的结果,如authorizeForResponse()authorizeForAccessToken(),前者是将授权请求后响应结果原本返回,而后者是只提取access token字符串返回。另外在授权时,可以选择是否传入用户信息TUser参数。

简单示例如下:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testAuthorize() {
    TClient client1 = oauth2Template.mustGetClient("client1");
    TOAuth2AuthorizationOperations authorizationOps = oauth2Template.authorization(client1);
    
    // 获取纯客户端身份的 access token
    String accessToken = authorizationOps.authorizeForAccessToken();
    // 获取携带用户身份的 access token
    TUser user1 = oauth2Template.mustGetUser("user1");
    accessToken = authorizationOps.authorizeForAccessToken(user1);
}
  • TOAuth2AuthorizationOperations flow(Flow flow)

修改授权身份验证方式,默认为Flow.CODE授权码流,可以切换为隐式流。注意,该方法为创建一个新对象,新对象才应用新的授权方式。

# Token 操作

TOAuth2TokenOperations接口提供OAuth2 token端点操作。一般通过TOAuth2Template或者TOAuth2Client等接口的token方法获取对象,示例:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testToken() {
    // 方式1
    TClient client1 = oauth2Template.mustGetClient("client1");
    TOAuth2TokenOperations tokenOps1 = oauth2Template.token(client1);
    
    // 方式2
    TOAuth2Client clientOps = oauth2Template.client("client1");
    TOAuth2TokenOperations tokenOps2 = clientOps.token();
    
    // ...
}

示例中的client1均是从安全配置的测试客户端列表中获取,后续的授权都基于该客户端身份。常用方法有:

下列方法中的*表示通配符,有一定的命名规则。

  • get*ByCode(String code)

根据authorization code请求获取token等不同返回类型的信息,如String getAccessTokenByCode(String code),返回为access token字符串。

  • get*ByClientCredentials()

采用客户端公钥、密钥方式获取token等不同返回类型的信息,只携带客户端身份,所获取的access token只能用于访问与用户无关的API

  • get*ByRefreshToken(String refreshToken)

根据refresh token重新获取token信息。

# 令牌内省操作

TOAuth2IntrospectOperations接口提供OAuth2 Introspection端点操作。OAuth2 Introspection提供了一种机制以获取有关访问令牌的信息,检查访问令牌的有效性,和获取如关联的用户和scope等。

一般通过TOAuth2Template或者TOAuth2Client等接口的introspection方法获取对象,简单示例:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testIntrospect() 
    TClient client1 = oauth2Template.mustGetClient("client1");
    TOAuth2IntrospectOperations introspectOps = oauth2Template.introspection(client1);
	
	String accessToken = "...";
	// 调用 {oauth2.server-url}/oauth2/introspect
	TTokenInfo tokenInfo = introspectOps.introspect(accessToken);

	// 简化:可以直接使用 TOAuth2Template 的 introspect 方法
	tokenInfo = oauth2Template.introspect(client1, accessToken);
	// 单元测试只有 TUser 的情况下,自动获取 access token
	TUser user1 = oauth2Template.mustGetUser("user1");
	tokenInfo = oauth2Template.introspect(client1, user1);
}

# 用户操作

TOAuth2UserInfoOperations接口提供用户信息相关的操作,可以通过TAuth2Templateuserinfo方法获取对象:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testUserinfo() {
    TOAuth2UserInfoOperations userinfoOps = oauth2Template.userinfo();
    // 从 access token 获取用户信息
    String accessToken = "...";
    TUserInfo user = userinfoOps.request(accessToken);
}

# 客户端操作

上面几节中,我们可以了解到授权、令牌等操作在创建对象时需要传入客户端参数,在测试中,如果已经明确用使同一个客户端且会用到多个类似这些的操作,可以使用TOAuth2Client,该接口包装了与客户端相关的操作方法,只要在创建TOAuth2Client时设置一次客户端,后续获取到的授权操作等对象都是围绕该客户端进行。

可以通过TOAuth2Templateclient方法获取对象,简单示例:

@Autowired
protected TOAuth2Template oauth2Template;

...
    
@Test
public void testClient() {
    TOAuth2Client clientOps = oauth2Template.client("client1");
    
    // 获取授权操作对象
    TOAuth2AuthorizationOperations authorizationOps = client.authorization();
    // 获取 token 操作对象
    TOAuth2TokenOperations tokenOps = clientOps.token();
    // 获取令牌内省操作对象
    TOAuth2IntrospectOperations introspectOps = clientOps.introspection();
    
    // 一些常用方法可以直接从 TOAuth2Client 调用,无需先获取对应的操作对象
    // tokenOps.getAccessTokenByClientCredentials();
    clientOps.authenticateForAccessToken();
}
顶部