春季啓動1.5引入了test slices像@WebMvcTest
。使用這些測試片並手動加載OAuth2AutoConfiguration
會使您的測試更少的樣板化,並且它們的運行速度會比建議的基於@SpringBootTest
的解決方案更快。如果您還導入生產安全配置,則可以測試配置的過濾器鏈是否適用於您的Web服務。
這裏有一些額外的課程,你可能會發現有益沿着設置:
控制器:
@RestController
@RequestMapping(BookingController.API_URL)
public class BookingController {
public static final String API_URL = "/v1/booking";
@Autowired
private BookingRepository bookingRepository;
@PreAuthorize("#oauth2.hasScope('myapi:write')")
@PatchMapping(consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
public Booking patchBooking(OAuth2Authentication authentication, @RequestBody @Valid Booking booking) {
String subjectId = MyOAuth2Helper.subjectId(authentication);
booking.setSubjectId(subjectId);
return bookingRepository.save(booking);
}
}
測試:
@RunWith(SpringRunner.class)
@AutoConfigureJsonTesters
@WebMvcTest
@Import(DefaultTestConfiguration.class)
public class BookingControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private JacksonTester<Booking> json;
@MockBean
private BookingRepository bookingRepository;
@MockBean
public ResourceServerTokenServices resourceServerTokenServices;
@Before
public void setUp() throws Exception {
// Stub the remote call that loads the authentication object
when(resourceServerTokenServices.loadAuthentication(anyString())).thenAnswer(invocation -> SecurityContextHolder.getContext().getAuthentication());
}
@Test
@WithOAuthSubject(scopes = {"myapi:read", "myapi:write"})
public void mustHaveValidBookingForPatch() throws Exception {
mvc.perform(patch(API_URL)
.header(AUTHORIZATION, "Bearer foo")
.content(json.write(new Booking("myguid", "aes")).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is2xxSuccessful());
}
}
DefaultTestConfiguration:
@TestConfiguration
@Import({MySecurityConfig.class, OAuth2AutoConfiguration.class})
public class DefaultTestConfiguration {
}
MySecurityConfig(這是用於生產):
@Configuration
@EnableOAuth2Client
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/v1/**").authenticated();
}
}
自定義註釋從測試注射範圍:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithOAuthSubjectSecurityContextFactory.class)
public @interface WithOAuthSubject {
String[] scopes() default {"myapi:write", "myapi:read"};
String subjectId() default "a1de7cc9-1b3a-4ecd-96fa-dab6059ccf6f";
}
工廠類處理自定義註解 :
public class WithOAuthSubjectSecurityContextFactory implements WithSecurityContextFactory<WithOAuthSubject> {
private DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
@Override
public SecurityContext createSecurityContext(WithOAuthSubject withOAuthSubject) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
// Copy of response from https://myidentityserver.com/identity/connect/accesstokenvalidation
Map<String, ?> remoteToken = ImmutableMap.<String, Object>builder()
.put("iss", "https://myfakeidentity.example.com/identity")
.put("aud", "oauth2-resource")
.put("exp", OffsetDateTime.now().plusDays(1L).toEpochSecond() + "")
.put("nbf", OffsetDateTime.now().plusDays(1L).toEpochSecond() + "")
.put("client_id", "my-client-id")
.put("scope", Arrays.asList(withOAuthSubject.scopes()))
.put("sub", withOAuthSubject.subjectId())
.put("auth_time", OffsetDateTime.now().toEpochSecond() + "")
.put("idp", "idsrv")
.put("amr", "password")
.build();
OAuth2Authentication authentication = defaultAccessTokenConverter.extractAuthentication(remoteToken);
context.setAuthentication(authentication);
return context;
}
}
我使用我們的身份服務器的響應副本創建一個現實的OAuth2Authentication
。你大概可以複製我的代碼。如果您想重複身份服務器的過程,請在org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication
或org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication
中放置一個斷點,具體取決於您是否配置了自定義ResourceServerTokenServices
或不是。
你能提供一個你的測試是什麼樣子的例子嗎?你只是測試基於安全性的方法嗎?你在使用MockMvc嗎?您是否將實際的REST呼叫轉到您的服務? –
@RobWinch我已經使用每種方法添加了示例代碼,並理解它爲什麼不起作用。我正在尋找可以在測試安全性方面工作的方法。 – Tim
謝謝你,所有的代碼。 運行testHelloUser#MyControllerIT.java時,我似乎得到了401。 你能幫我解決這個問題嗎? – myspri