web-dev-qa-db-ja.com

SpringエンドポイントをテストするときにNestedServletExceptionを防ぐ方法は?

@PreAuthorize(#oauth2.hasScope('scope')で保護されているエンドポイントの一部についてセキュリティ設定をテストするを試みています。必要なスコープを持たないアクセストークンを使用してPostman経由でそのようなエンドポイントにアクセスすると、HTTPステータスコード403(禁止)とともに以下が返されます。

{
    "error": "insufficient_scope",
    "error_description": "Insufficient scope for this resource",
    "scope": "scope"
}

これは私が望む期待される行動です。

この構成をテストしようとすると、Springs NestedServletExceptionが期待どおりの結果を完了する前に、テストケースに干渉します。

これは私がテストしたいコントローラの簡易バージョンです:

@RestController
@RequestMapping(value = "/api")
public class OauthTestingResource {

    @PreAuthorize(#oauth2.hasScope('scope'))
    @RequestMapping(value = "/scope", method = RequestMethod.GET)
    public void endpoint() {
        // ...
    }
}

そして、これは対応するテストケースです:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyApplication.class)
@WebAppConfiguration
public class AuthorizationTest {

    @Autowired
    protected WebApplicationContext webApplicationContext;

    protected SecurityContext securityContext = Mockito.mock(SecurityContext.class);

    @Before
    public void setup() throws Exception {

        this.mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        SecurityContextHolder.setContext(securityContext);
    }

    protected Authentication createMockAuth(Client client) {

        final List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        final Authentication pwAuth = new UsernamePasswordAuthenticationToken("testuser", "testpw", authorities);

        final TokenRequest request = new TokenRequest(new HashMap<>(), client.getClientId(), client.getScopes(), "password");

        final OAuthClient oauthClient = new OAuthClient(client, GrantType.PASSWORD);

        return new OAuth2Authentication(request.createOAuth2Request(oauthClient), pwAuth);
    }
    @Test
    public void testAppScope() throws Exception {

        final Client client = new Client("id1", "secret1");

        client.setScope("scope");
        Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
        // this test passes
        mvc.perform(get("/api/scope")).andExpect(status().isOk()); 

        client.setScope("other_scope");
        Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
        // NestedServletException thrown here
        mvc.perform(get("/api/scope")).andExpect(status().isForbidden()); 
    }
}

スローされる例外は次のとおりです(これは予想されます)。

org.springframework.web.util.NestedServletException:リクエスト処理が失敗しました。ネストされた例外はorg.springframework.security.access.AccessDeniedException:このリソースのスコープが不十分です

私の質問はこの例外がテストケースを妨害しないようにするにはどうすればよいですか?

11
Philipp Jahoda

この link に従って、春のセキュリティテストケースを実行しました。元の例外をNestedServletExceptionにネストするというこの問題を除いて、問題はありませんでした。私はこれを直接理解する方法を見つけられませんでしたが、AspectJはこれをよりクリーンな方法で処理するのに役立ちました。

Assertionsクラスの静的なassertThatThrownBy()メソッドを使用できます。このメソッドは、スローされた例外のアサーションを書き込むために使用できるAbstractThrowableAssertオブジェクトを返します。

MethodThatThrowsException()メソッドによってスローされた例外をキャプチャするコードは、次のようになります。

assertThatThrownBy(() -> methodThatThrowsException())
.isExactlyInstanceOf(DuplicateEmailException.class);

this のおかげで、追加の詳細を見つけることができる優れたブログになりました。

私のテストケースでこれを処理する方法は(テストケースのコードラインを取得することによって)なります。

org.assertj.core.api.Assertions.assertThatThrownBy(() -> mvc.perform(get("/api/scope")).andExpect(status().isOk())).hasCause(new AccessDeniedException("Access is denied"));

そうすることで、テストケースは、NestedServletExceptionにネストされている実際のAccessDeniedExceptionをアサートできるようになります。

13
Vishal

その例外に@ExceptionHandlerを追加して修正しました。 MockMvcが実際の例外をスローする場合、これは理想的ではないこのケースを「処理」しないことを意味するようです。

1
Sam