我花了一天的时间来弄清楚如何解决这个问题。我最终得到了一个我认为还不错的解决方案,并且可以帮助很多人。
根据您在测试中尝试执行的操作,您可以执行以下操作mockMvc
测试您的控制器。请注意,未调用 AuthorizationServer。您仅留在资源服务器中进行测试。
- 创建一个豆子
InMemoryTokenStore
将用于OAuth2AuthenticationProcessingFilter
来验证您的用户。很棒的是您可以将代币添加到您的InMemoryTokenStore
在执行测试之前。这OAuth2AuthenticationProcessingFilter
将根据用户正在使用的令牌对用户进行身份验证,并且不会调用任何远程服务器。
@Configuration
public class AuthenticationManagerProvider {
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
- 注释
@WithMockUser
不适用于 OAuth2。确实,OAuth2AuthenticationProcessingFilter
始终检查您的令牌,不考虑SecurityContext
。我建议使用与@WithMockUser
,但使用您在代码库中创建的注释。 (进行一些易于维护和清洁的测试):
@WithMockOAuth2Scope
包含自定义身份验证所需的几乎所有参数。您可以删除那些您永远不会使用的内容,但我做了很多工作以确保您看到其中的可能性。 (将这两个类放入您的测试文件夹中)
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public @interface WithMockOAuth2Scope {
String token() default "";
String clientId() default "client-id";
boolean approved() default true;
String redirectUrl() default "";
String[] responseTypes() default {};
String[] scopes() default {};
String[] resourceIds() default {};
String[] authorities() default {};
String username() default "username";
String password() default "";
String email() default "";
}
然后,我们需要一个类来解释此注释,并用测试所需的数据填充我们的“InMemoryTokenStore”。
@Component
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
@Autowired
private TokenStore tokenStore;
@Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2AccessToken oAuth2AccessToken = createAccessToken(mockOAuth2Scope.token());
OAuth2Authentication oAuth2Authentication = createAuthentication(mockOAuth2Scope);
tokenStore.storeAccessToken(oAuth2AccessToken, oAuth2Authentication);
return SecurityContextHolder.createEmptyContext();
}
private OAuth2AccessToken createAccessToken(String token) {
return new DefaultOAuth2AccessToken(token);
}
private OAuth2Authentication createAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2Request oauth2Request = getOauth2Request(mockOAuth2Scope);
return new OAuth2Authentication(oauth2Request,
getAuthentication(mockOAuth2Scope));
}
private OAuth2Request getOauth2Request(WithMockOAuth2Scope mockOAuth2Scope) {
String clientId = mockOAuth2Scope.clientId();
boolean approved = mockOAuth2Scope.approved();
String redirectUrl = mockOAuth2Scope.redirectUrl();
Set<String> responseTypes = new HashSet<>(asList(mockOAuth2Scope.responseTypes()));
Set<String> scopes = new HashSet<>(asList(mockOAuth2Scope.scopes()));
Set<String> resourceIds = new HashSet<>(asList(mockOAuth2Scope.resourceIds()));
Map<String, String> requestParameters = Collections.emptyMap();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
return new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
}
private Authentication getAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
String username = mockOAuth2Scope.username();
User userPrincipal = new User(username,
mockOAuth2Scope.password(),
true, true, true, true, grantedAuthorities);
HashMap<String, String> details = new HashMap<>();
details.put("user_name", username);
details.put("email", mockOAuth2Scope.email());
TestingAuthenticationToken token = new TestingAuthenticationToken(userPrincipal, null, grantedAuthorities);
token.setAuthenticated(true);
token.setDetails(details);
return token;
}
}
- 一切设置完毕后,在下创建一个简单的测试类
src/test/java/your/package/
。这堂课将做mockMvc
操作,并使用@ WithMockOAuth2Scope
创建测试所需的令牌。
@WebMvcTest(SimpleController.class)
@Import(AuthenticationManagerProvider.class)
class SimpleControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockOAuth2Scope(token = "123456789",
authorities = "CAN_READ")
public void test() throws Exception {
mockMvc.perform(get("/whoami")
.header("Authorization", "Bearer 123456789"))
.andExpect(status().isOk())
.andExpect(content().string("username"));
}
}
我想出了这个解决方案,感谢:
- 如何测试 spring-security-oauth2 资源服务器安全性? https://stackoverflow.com/questions/29510759/how-to-test-spring-security-oauth2-resource-server-security
- 在 Spring 中伪造 OAuth2 单点登录,有两种方法 http://engineering.pivotal.io/post/faking_oauth_sso/
- 注释解决方案 https://github.com/spring-projects/spring-security/issues/6557
- 流畅的API解决方案 https://github.com/spring-projects/spring-security/issues/6634
- 大量的调试,我在 Spring 中迷失了自己,但我终于找到了我正在寻找的东西。
对于好奇的人:
测试时,Spring加载一个InMemoryTokenStore
,并让您可以选择您提供的一个(作为@Bean
)。在生产中运行时,就我而言,Spring 使用RemoteTokenStore
,它调用远程授权服务器来检查令牌(http://authorization_server/oauth/check_token
).
当您决定使用 OAuth2 时,Spring 会触发OAuth2AuthenticationProcessingFilter
。这是我所有调试会话期间的入口点。
我学到了很多东西,谢谢你。
您可以找到源代码在这里 https://github.com/truar/oauth2-test-utils.
希望它会有所帮助!