[英]Test JwtDecoder in @WebMvcTest with Spring Security
我正在使用 Spring Boot 2.2.1 和spring-security-oauth2-resource-server:5.2.0.RELEASE
。 我想編寫一個集成測試來測試安全性是否可以。
我在我的應用程序中定義了這個WebSecurityConfigurerAdapter
:
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final OAuth2ResourceServerProperties properties;
private final SecuritySettings securitySettings;
public WebSecurityConfiguration(OAuth2ResourceServerProperties properties, SecuritySettings securitySettings) {
this.properties = properties;
this.securitySettings = securitySettings;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder result = NimbusJwtDecoder.withJwkSetUri(properties.getJwt().getJwkSetUri())
.build();
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(
JwtValidators.createDefault(),
new AudienceValidator(securitySettings.getApplicationId()));
result.setJwtValidator(validator);
return result;
}
private static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String applicationId;
public AudienceValidator(String applicationId) {
this.applicationId = applicationId;
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
if (token.getAudience().contains(applicationId)) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token", "The audience is not as expected, got " + token.getAudience(),
null));
}
}
}
}
它有一個自定義驗證器來檢查令牌中的受眾( aud
)聲明。
我目前有這個測試,它有效,但它根本不檢查觀眾聲明:
@WebMvcTest(UserController.class)
@EnableConfigurationProperties({SecuritySettings.class, OAuth2ResourceServerProperties.class})
@ActiveProfiles("controller-test")
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testOwnUserDetails() throws Exception {
mockMvc.perform(get("/api/users/me")
.with(jwt(createJwtToken())))
.andExpect(status().isOk())
.andExpect(jsonPath("userId").value("AZURE-ID-OF-USER"))
.andExpect(jsonPath("name").value("John Doe"));
}
@Test
void testOwnUserDetailsWhenNotLoggedOn() throws Exception {
mockMvc.perform(get("/api/users/me"))
.andExpect(status().isUnauthorized());
}
@NotNull
private Jwt createJwtToken() {
String userId = "AZURE-ID-OF-USER";
String userName = "John Doe";
String applicationId = "AZURE-APP-ID";
return Jwt.withTokenValue("fake-token")
.header("typ", "JWT")
.header("alg", "none")
.claim("iss",
"https://b2ctestorg.b2clogin.com/80880907-bc3a-469a-82d1-b88ffad655df/v2.0/")
.claim("idp", "LocalAccount")
.claim("oid", userId)
.claim("scope", "user_impersonation")
.claim("name", userName)
.claim("azp", applicationId)
.claim("ver", "1.0")
.subject(userId)
.audience(Set.of(applicationId))
.build();
}
}
我還有一個包含應用程序 ID 和 jwt-set-uri 的controller-test
配置文件的屬性文件:
security-settings.application-id=FAKE_ID
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://b2ctestorg.b2clogin.com/b2ctestorg.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_ropc_flow
也許沒有使用 JwtDecoder 因為 Jwt 是手動創建的? 我如何確保在測試中調用了 JwtDecoder?
為了詳細說明 Eleftheria Stein-Kousathana 的答案,我做了以下更改以使其成為可能:
1) 創建一個JwtDecoderFactoryBean
class 以便能夠對JwtDecoder
和配置的驗證器進行單元測試:
@Component
public class JwtDecoderFactoryBean implements FactoryBean<JwtDecoder> {
private final OAuth2ResourceServerProperties properties;
private final SecuritySettings securitySettings;
private final Clock clock;
public JwtDecoderFactoryBean(OAuth2ResourceServerProperties properties,
SecuritySettings securitySettings,
Clock clock) {
this.properties = properties;
this.securitySettings = securitySettings;
this.clock = clock;
}
@Override
public JwtDecoder getObject() {
JwtTimestampValidator timestampValidator = new JwtTimestampValidator();
timestampValidator.setClock(clock);
JwtIssuerValidator issuerValidator = new JwtIssuerValidator(securitySettings.getJwtIssuer());
JwtAudienceValidator audienceValidator = new JwtAudienceValidator(securitySettings.getJwtApplicationId());
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(
timestampValidator,
issuerValidator,
audienceValidator);
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(properties.getJwt().getJwkSetUri())
.build();
decoder.setJwtValidator(validator);
return decoder;
}
@Override
public Class<?> getObjectType() {
return JwtDecoder.class;
}
}
我還將AudienceValidator
從原始代碼中提取到外部 class 並將其重命名為JwtAudienceValidator
。
2) 從安全配置中刪除JwtDecoder
@Bean
方法,如下所示:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
3)在一些@Configuration
class中創建一個Clock
bean:
@Bean
public Clock clock() {
return Clock.systemDefaultZone();
}
(這是對令牌時間到期的單元測試所必需的)
使用此設置,現在可以為JwtDecoder
設置編寫單元測試,這是應用程序使用的實際設置:
// actual @Test methods ommitted, but they can use this private method
// to setup a JwtDecoder and test some valid/invalid JWT tokens.
@NotNull
private JwtDecoder createDecoder(String currentTime, String issuer, String audience) {
OAuth2ResourceServerProperties properties = new OAuth2ResourceServerProperties();
properties.getJwt().setJwkSetUri(
"https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_ropc_flow");
JwtDecoderFactoryBean factoryBean = new JwtDecoderFactoryBean(properties,
new SecuritySettings(audience, issuer),
Clock.fixed(Instant.parse(currentTime),
ZoneId.systemDefault()));
//noinspection ConstantConditions - getObject never returns null in this case
return factoryBean.getObject();
}
最后, @WebMvcTest
需要有一個模擬JwtDecoder
,因為真正的 JwtDecoder 不再使用@WebMvcTest
測試片啟動(由於使用了工廠 bean)。 這是很好的 IMO,否則,我需要為真正的JwtDecoder
定義無論如何都沒有使用的屬性。 因此,我在測試中不再需要controller-test
配置文件。
所以只需聲明一個這樣的字段:
@MockBean
private JwtDecoder jwtDecoder;
或創建嵌套測試配置 class:
@TestConfiguration
static class TestConfig {
@Bean
public JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
}
通過使用 JWT 后處理器.with(jwt(createJwtToken())))
您可以繞過JwtDecoder
。
考慮如果沒有繞過JwtDecoder
會發生什么。
在過濾器鏈中,您的請求將到達JwtDecoder
解析 JWT 值的點。
在這種情況下,該值為"fake-token"
,這將導致異常,因為它不是有效的 JWT。
這意味着代碼甚至不會到達調用AudienceValidator
的位置。
您可以將傳遞給SecurityMockMvcRequestPostProcessors.jwt(Jwt jwt)
的值視為將從JwtDecoder.decode(String token)
返回的響應。
然后,使用SecurityMockMvcRequestPostProcessors.jwt(Jwt jwt)
的測試將測試提供有效 JWT 令牌時的行為。
您可以為AudienceValidator
添加其他測試以確保其正常運行。
我的猜測是,mockMvc 沒有配置為考慮安全方面 (1),或者@WebMvcTest
測試片沒有自動配置所有必需的 bean (2)。
1:您可以嘗試將@AutoConfigureMockMvc
添加到class,或者使用手動配置mockMvc
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
2:如果和@WebMvcTest
測試切片相關,考慮在測試class中加入@Import(WebSecurityConfig.class)
。 否則,在測試@AutoConfigureMockMvc
上使用@SpringBootTest
和 @AutoConfigureMockMvc 而不是@WebMvcTest
來設置 Spring 引導測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.