📘
Springでリソースサーバのテストコード書く
spring-security-oauth2-resource-server
を利用してリソースサーバを作成した際に、認可が必要なAPIのテストコードを書く方法です。
記事の環境
- SpringBoot 3.1.2
- WireMock(wiremock-jre8-standalone) 2.35.0
テスト対象となるコード
まずテスト対象のリソースサーバのコードになります。
APIとしてscopeにmessage.read
がついているもののみを許可します。
HelloController.java
@RestController
@RequestMapping("/")
public class HelloController {
@GetMapping
@PreAuthorize("hasAuthority('SCOPE_message.read')")
public String index(Principal principal) {
return principal.getName();
}
}
すべてのエンドポイントは認証必須とし、OAuth2のJWTを利用した認可を行うように設定します。
SecurityConfig
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}
認可サーバはlocalhost:8080
に存在するものとします。
application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri: http://localhost:8080
server.port=8081
テストコード
テストとしては、認可サーバをモックすることでテスト可能にします。
まずJWTを生成する際の暗号を行うBeanを作成します。
SecurityTestConfig.java
@Configuration
public class SecurityTestConfig {
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public JwtEncoder jwtEncoderGenerator(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwtEncoder(jwkSource);
}
}
次はテストコードです。
HelloControllerTest.java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@WireMockTest(httpPort = 8080)
class HelloControllerTest {
@Autowired
private JWKSource<SecurityContext> jwtSource;
@Autowired
private JwtEncoder jwtEncoder;
@Autowired
private WebTestClient webTestClient;
@Test
void scopeが正しい場合はsubjectを返却() {
stubFor(get("/.well-known/openid-configuration")
.willReturn(okJson("""
{
"issuer": "http://localhost:8080",
"jwks_uri": "http://localhost:8080/oauth2/jwks"
}
""")));
JWKSelector selector = new JWKSelector(new JWKMatcher.Builder().build());
JWKSet jwkSet = null;
try {
jwkSet = new JWKSet(this.jwtSource.get(selector, null));
} catch (KeySourceException e) {
throw new RuntimeException(e);
}
stubFor(get("/oauth2/jwks").willReturn(okJson(jwkSet.toString())));
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256).build();
JwtClaimsSet claimsSet = JwtClaimsSet.builder()
.subject("taro")
.issuer("http://localhost:8080")
.claim("scope", "message.read")
.build();
Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claimsSet));
String token = jwt.getTokenValue();
webTestClient.get()
.uri("/")
.headers(headers -> headers.setBearerAuth(token))
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("taro");
}
}
わかりやすくするために1メソッドにまとめましたが、実際には共通化するなどして様々なテストケースで利用できるようにしてください。
重要な部分としてはJwtClaimsSet
の生成箇所でsubjectを変更したり、scopeを設定したりすることで、テストケースごとに必要なJWTを生成してください。
Discussion