如何解决资源服务器的春季集成测试基于spring-cloud-starter-oauth2
我正在将Spring Boot和Spring Cloud用于oAuth2资源服务器。这是配置:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
...
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
ResourceServerConfig
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${my-app.security.audience}")
private String audience;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(audience);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.httpBasic().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests(authorize -> authorize
.antMatchers("/actuator/**").permitAll() // Todo: Enable basic auth for actuator
.anyRequest().authenticated()
);
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**",corsConfiguration);
return source;
}
@Bean
public ResourceServerProperties resourceServerProperties() {
return new ResourceServerProperties(null,null);
}
}
OidcJwkTokenStoreConfig
@Configuration
public class OidcJwkTokenStoreConfig {
private final ResourceServerProperties resource;
public OidcJwkTokenStoreConfig(ResourceServerProperties resource) {
this.resource = resource;
}
@Bean
public TokenStore jwkTokenStore(UserDetailsService userDetailsService) {
DefaultAccesstokenConverter tokenConverter = new DefaultAccesstokenConverter();
tokenConverter.setUserTokenConverter(new MvcUserAuthenticationConverter(userDetailsService));
return new JwkTokenStore(this.resource.getJwk().getKeySetUri(),tokenConverter);
}
}
public class MvcUserAuthenticationConverter implements UserAuthenticationConverter {
private final String SUB = "sub";
private final UserDetailsService userDetailsService;
public MvcUserAuthenticationConverter(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Map<String,?> convertUserAuthentication(Authentication userAuthentication) {
throw new UnsupportedOperationException();
}
@Override
public Authentication extractAuthentication(Map<String,?> map) {
if (map.containsKey(SUB)) {
Object principal = map.get(SUB);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(SUB));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal,"N/A",authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String,?> map) {
if (!map.containsKey(AUTHORITIES)) {
return null;
}
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
如何进行集成测试?
单元测试不是一个大问题。但是当涉及到集成测试时,我很挣扎。如何模拟/跳过提供真实的承载令牌?首选的解决方案是使用mockmvc
进行集成测试。
到目前为止,我得到了以下内容:
@SpringBoottest
@Testcontainers
@ContextConfiguration(
initializers = ProjectResourceTest.Initializer.class,classes = {ProjectResourceTest.ApiTestConfiguration.class,MyApplication.class}
)
@AutoConfiguremockmvc
public class ProjectResourceTest {
@Container
private static final MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.2.5");
public static class Initializer implements ApplicationContextinitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues values = TestPropertyValues.of(
"spring.data.mongodb.uri=" + mongoDB.getreplicasetUrl()
);
values.applyTo(configurableApplicationContext);
}
}
@TestConfiguration
public static class ApiTestConfiguration {
@Bean
@Primary
public UserDetailsService userDetailsService() {
MvcUser defaultUser = new MvcUser("default-user","Default User");
return new InMemoryUserDetailsManager(singletonList(defaultUser));
}
}
@Autowired
private mockmvc mockmvc;
@Autowired
MongoTemplate mongo;
@Test
@WithUserDetails("default-user")
void shouldReturnAllProjects() throws Exception {
ProjectEntity project = ProjectFaker.newProjectEntity();
mongo.save(project);
mockmvc.perform(get("/api/v1/projects"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.key",is(project.getKey())))
.andExpect(jsonPath("$.name",is(project.getName())))
.andExpect(jsonPath("$.createdAt",is(project.getCreatedAt())));
}
}
但是这种方法以以下异常结束
java.lang.IllegalStateException: Unable to create SecurityContext using @org.springframework.security.test.context.support.WithUserDetails(setupBefore=TEST_METHOD,userDetailsServiceBeanName=,value=default-user)
at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.createTestSecurityContext(WithSecurityContextTestExecutionListener.java:126)
at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.createTestSecurityContext(WithSecurityContextTestExecutionListener.java:96)
at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.beforeTestMethod(WithSecurityContextTestExecutionListener.java:62)
at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:289)
at org.springframework.test.context.junit.jupiter.SpringExtension.beforeEach(SpringExtension.java:108)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachCallbacks$1(TestMethodTestDescriptor.java:161)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:197)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:197)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachCallbacks(TestMethodTestDescriptor.java:160)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
at com.intellij.junit5.junit5IdeaTestRunner.startRunnerWithArgs(junit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.beans.factory.NoSuchBeanDeFinitionException: No qualifying bean of type 'org.springframework.security.core.userdetails.UserDetailsService' available
at org.springframework.beans.factory.support.DefaultListablebeanfactory.getBean(DefaultListablebeanfactory.java:351)
at org.springframework.beans.factory.support.DefaultListablebeanfactory.getBean(DefaultListablebeanfactory.java:342)
at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.findUserDetailsService(WithUserDetailsSecurityContextFactory.java:78)
at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:58)
at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:44)
at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.createTestSecurityContext(WithSecurityContextTestExecutionListener.java:123)
... 61 more
我的自定义UserDetailsService
@Service
public class MvcUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
public MvcUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserEntity> user = userRepository.findByUserId(username);
if (user.isPresent()) {
return new MvcUser(user.get());
} else {
return createNewUser();
}
}
...
}
我在这里想念什么?
解决方法
您可以执行以下操作:
- 为测试覆盖资源服务器配置,并确保在类路径上放置了有效的RSA公钥(例如
/src/test/resources
)
application.yml
中的示例/src/test/resources
:
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:id_rsa.pub
这应该可以使您的应用程序启动,并且不需要与授权服务器进行任何HTTP通信。
-
接下来,结合使用
@SpringBootTest
和@AutoConfigureMockMvc
对模拟的Servlet环境进行测试。确保您拥有spring-security-test
依赖项。 -
摆脱测试中的自定义
UserDetailsService
bean,而是依靠常规的自动配置。 -
现在您可以使用例如
@WithMockUser
,以便在测试时在Spring SecurityContext中提供模拟用户。
要在集成测试过程中提供真实的承载令牌,可以执行以下操作:
- 覆盖您的配置以指向伪造的授权服务器(例如,使用WireMock启动本地HTTP服务器)
- 使用您的应用程序请求JWKS的授权服务模拟应用程序(充当资源服务器)的初始HTTP通信。您可以在其中创建内存中的RSA密钥对,并让模拟的身份提供者返回公钥
- 创建一个有效的JWT并使用私钥对其进行签名
- 使用例如访问您启动的应用程序
WebTestClient
或TestRestTemplate
并将令牌添加为请求标头的一部分(为此您需要@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。