Spring Boot webservice (REST) - 如何将 JUnit 5 测试从基本身份验证更改为 OAuth2 (Keycloak)
Posted
技术标签:
【中文标题】Spring Boot webservice (REST) - 如何将 JUnit 5 测试从基本身份验证更改为 OAuth2 (Keycloak)【英文标题】:Spring Boot webservice (REST) - How to change JUnit 5 tests from basic authentication to OAuth2 (Keycloak) 【发布时间】:2021-11-01 22:19:18 【问题描述】:我有一个带有REST
控制器和基本身份验证(用户名和密码)的Spring Boot
网络服务。
在此基础上我开发了 JUnit 5 测试。
现在我切换到OAuth2
,目前正在尝试Resource Owner Password Credentials
授权类型。
我需要对我的JUnit 5
测试进行哪些更改才能现在使用OAuth2
运行?
当然,在使用 OAuth2
运行我的新测试之前,我必须先开始 Keycloak
,然后再进行测试。
以下是我对当前基本身份验证和新 OAuth2 的设置。
基本身份验证(旧实现)
在我的网络服务端,网络安全配置类如下所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
httpSecurity
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/articles/**").hasRole("ADMIN")
// More antMatchers...
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().disable();
@Bean
@Override
public UserDetailsService userDetailsService()
UserDetails admin = User
.withUsername("admin")
.password("noop" + "admin123")
.roles("ADMIN")
.build();
// More users...
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(admin);
...
return userDetailsManager;
对于 JUnit 5 测试,我总是使用用户 admin
,例如
@SpringBootTest
@AutoConfigureMockMvc
@WithUserDetails(value = "admin")
@TestInstance(Lifecycle.PER_CLASS)
public class MyRestControllerMockMvcTest
@Autowired
private MockMvc mockMvc;
@BeforeAll
public void init(ApplicationContext appContext) throws Exception
TestUtils.setupSecurityContext(appContext);
// some initialization
@AfterAll
public void cleanup(ApplicationContext appContext) throws Exception
TestUtils.setupSecurityContext(appContext);
// some cleanup
@Test
public void getSomeInformationFromMyRestController() throws Exception
MvcResult mvcResult = TestUtils.performGet(mockMvc, "...REST controller endpoint...", status().isOk());
MockHttpServletResponse response = mvcResult.getResponse();
ObjectMapper objectMapper = new ObjectMapper();
... = objectMapper.readValue(response.getContentAsString(), ...);
assertNotNull(...);
public class TestUtils
public static void setupSecurityContext(ApplicationContext appContext)
UserDetailsService uds = (UserDetailsService) appContext.getBean("userDetailsService");
UserDetails userDetails = uds.loadUserByUsername ("admin");
Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
public static MvcResult performGet(MockMvc mockMvc, String endpoint, ResultMatcher status) throws Exception
MvcResult mvcResult = mockMvc.perform(get(endpoint))
.andDo(print())
.andExpect(status)
.andReturn();
return mvcResult;
现在查看@BeforeAll
和@AfterAll
中的测试设置,我突然不确定是否必须这样做
TestUtils.setupSecurityContext(appContext);
因为我现在用
@WithUserDetails(value = "admin")
@TestInstance(Lifecycle.PER_CLASS)
在课堂上。只是好奇如果没有TestUtils.setupSecurityContext(appContext);
,测试是否仍能运行,将尝试。
OAUTH2(新实现,替换上面的基本身份验证)
application.properties
...
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8183/auth/realms/myrealm/protocol/openid-connect/certs
使用OAuth2
,我将我的网络服务(资源服务器)中的网络安全配置类更改如下:
@EnableWebSecurity
public class WebSecurityConfig
@Bean
SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception
httpSecurity
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/articles/**").hasRole("ADMIN")
// More antMatchers...
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter())
;
return httpSecurity.build();
private JwtAuthenticationConverter jwtAuthenticationConverter()
final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new MyRoleConverter());
return jwtAuthenticationConverter;
public class MyRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>>
@Override
public Collection<GrantedAuthority> convert(final Jwt jwt)
jwt.getClaims().get("realm_access");
// Create roles
return ...;
我的用户现在在Keycloak
中定义。
Keycloak
配置为使用Resource Owner Password Credentials
。
【问题讨论】:
在大多数情况下,身份验证方式对您的控制器来说并不重要,所以我想知道您是否必须做任何不同的事情。你有问题吗? 是否有可能运行测试以完全破坏安全性?怎么样? 在测试类上将此注释更改为@AutoConfigureMockMvc(secure = false),它不知道“安全”。 Spring Boot 2.5.4,Junit 5。 【参考方案1】:@jzheaux 是对的(当然,他是 spring-security 团队的成员......)。
您的安全配置会发生变化,但测试不会改变……在大多数情况下:您可能希望在测试安全上下文中拥有一个正确类型的 Authentication
。
如果您的新安全配置使用JwtAuthenticationToken
填充安全上下文,那么在测试安全上下文中也有JwtAuthenticationToken
会很好。 @WithUserDetails(value = "admin")
不会构建 JwtAuthenticationToken
。
你应该看看我写的this lib,特别是@WithMockJwtAuth
。用法演示there:
@Test
@WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy"))
public void greetJwtCh4mpy() throws Exception
api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."));
附:
你会在同一个 git repo 示例中找到其他类型的 Authentication
比 JwtAuthenticationToken
更适合 OIDC,例如 KeycloakAuthenticationToken
(由 Keycloak 团队专门为 Keycloak 编写)或 OidcAuthentication
(由我自己为任何 OpenID Connect 编写兼容授权服务器),以及@WithMockKeycloakAuth
和@WithMockOidcAuth
【讨论】:
以上是关于Spring Boot webservice (REST) - 如何将 JUnit 5 测试从基本身份验证更改为 OAuth2 (Keycloak)的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot 开发WebService遇到的问题之一
spring boot整合cxf发布和调用webservice
使用 spring-boot 和 Weblogic 公开 SOAP Webservice
Spring Boot 实现RESTful webservice服务端实例