如何测试spring-security-oauth2 资源服务器的安全性?
How to test spring-security-oauth2 resource server security?
在发布 Spring Security 4 之后 improved support for testing 我想更新我当前的 Spring security oauth2 资源服务器测试。
目前我有一个助手 class,它使用 ResourceOwnerPasswordResourceDetails
设置一个 OAuth2RestTemplate
并测试 ClientId
连接到一个实际的 AccessTokenUri
请求我的测试的有效令牌。然后使用此 resttemplate 在我的 @WebIntegrationTest
s.
中发出请求
我想放弃对实际 AuthorizationServer 的依赖,并在我的测试中使用有效(如果有限)用户凭据,方法是利用 Spring Security 4 中的新测试支持。
到目前为止,我尝试使用 @WithMockUser
、@WithSecurityContext
、SecurityMockMvcConfigurers.springSecurity()
和 SecurityMockMvcRequestPostProcessors.*
的所有尝试都未能通过 MockMvc
进行经过身份验证的调用,并且我在 Spring 示例项目中找不到任何此类工作示例。
任何人都可以帮助我使用某种模拟凭据测试我的 oauth2 资源服务器,同时仍然测试强加的安全限制吗?
** 编辑 **
此处提供示例代码:https://github.com/timtebeek/resource-server-testing
对于每个测试 classes,我理解为什么它不能正常工作,但我正在寻找可以让我轻松测试安全设置的方法。
我现在正在考虑在 src/test/java
下创建一个非常宽松的 OAuthServer,这可能会有所帮助。有没有人有任何其他建议?
好的,我还无法使用新的 @WithMockUser
或相关注释测试我的独立 oauth2 JWT 令牌保护资源服务器。
作为解决方法,我已经能够通过设置 a permissive AuthorizationServer
under src/test/java, and having that define two clients I use through a helper class 来集成测试我的资源服务器安全性。这为我提供了一些方法,但它还没有我想测试各种用户、角色、范围等那么容易。
我猜从这里开始应该更容易实现我自己的 WithSecurityContextFactory
来创建 OAuth2Authentication
,而不是通常的 UsernamePasswordAuthentication
。但是,我还没有弄清楚如何轻松设置它的细节。欢迎任何关于如何设置的意见或建议。
为了有效地测试资源服务器的安全性,使用 MockMvc
和 RestTemplate
它有助于在 src/test/java
:
下配置 AuthorizationServer
授权服务器
@Configuration
@EnableAuthorizationServer
@SuppressWarnings("static-method")
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
public JwtAccessTokenConverter accessTokenConverter() throws Exception {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(SecurityConfig.key("rsa"));
jwt.setVerifierKey(SecurityConfig.key("rsa.pub"));
jwt.afterPropertiesSet();
return jwt;
}
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("myclientwith")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes("myscope")
.and()
.withClient("myclientwithout")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes(UUID.randomUUID().toString());
}
}
集成测试
对于集成测试,可以简单地使用内置的 OAuth2 测试支持规则和注释:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebIntegrationTest(randomPort = true)
@OAuth2ContextConfiguration(MyDetails.class)
public class MyControllerIT implements RestTemplateHolder {
@Value("http://localhost:${local.server.port}")
@Getter
String host;
@Getter
@Setter
RestOperations restTemplate = new TestRestTemplate();
@Rule
public OAuth2ContextSetup context = OAuth2ContextSetup.standard(this);
@Test
public void testHelloOAuth2WithRole() {
ResponseEntity<String> entity = getRestTemplate().getForEntity(host + "/hello", String.class);
assertTrue(entity.getStatusCode().is2xxSuccessful());
}
}
class MyDetails extends ResourceOwnerPasswordResourceDetails {
public MyDetails(final Object obj) {
MyControllerIT it = (MyControllerIT) obj;
setAccessTokenUri(it.getHost() + "/oauth/token");
setClientId("myclientwith");
setUsername("user");
setPassword("password");
}
}
MockMvc 测试
也可以使用 MockMvc
进行测试,但需要一个小帮手 class 来获得一个 RequestPostProcessor
来设置 Authorization: Bearer <token>
header 请求:
@Component
public class OAuthHelper {
// For use with MockMvc
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
AuthorizationServerTokenServices tokenservice;
OAuth2AccessToken createAccessToken(final String clientId) {
// Look up authorities, resourceIds and scopes based on clientId
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
// Default values for other parameters
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
// Create request
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes,
resourceIds, redirectUrl, responseTypes, extensionProperties);
// Create OAuth2AccessToken
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
}
然后您的 MockMvc
测试必须从 OauthHelper
class 获得 RequestPostProcessor
并在发出请求时通过它:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebAppConfiguration
public class MyControllerTest {
@Autowired
private WebApplicationContext webapp;
private MockMvc mvc;
@Before
public void before() {
mvc = MockMvcBuilders.webAppContextSetup(webapp)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
@Autowired
private OAuthHelper helper;
@Test
public void testHelloWithRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwith");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isOk());
}
@Test
public void testHelloWithoutRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwithout");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isForbidden());
}
}
GitHub 上提供了完整的示例项目:
https://github.com/timtebeek/resource-server-testing
我找到了一种更简单的方法来按照我在此处阅读的说明进行操作:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext。此解决方案特定于使用 #oauth2.hasScope
测试 @PreAuthorize
,但我相信它也可以适用于其他情况。
我创建了一个可以应用于 @Test
s 的注释:
WithMockOAuth2Scope
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public @interface WithMockOAuth2Scope {
String scope() default "";
}
WithMockOAuth2ScopeSecurityContextFactory
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import java.util.HashSet;
import java.util.Set;
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
@Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Set<String> scope = new HashSet<>();
scope.add(mockOAuth2Scope.scope());
OAuth2Request request = new OAuth2Request(null, null, null, true, scope, null, null, null, null);
Authentication auth = new OAuth2Authentication(request, null);
context.setAuthentication(auth);
return context;
}
}
示例测试使用 MockMvc
:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class LoadScheduleControllerTest {
private MockMvc mockMvc;
@Autowired
LoadScheduleController loadScheduleController;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(loadScheduleController)
.build();
}
@Test
@WithMockOAuth2Scope(scope = "dataLicense")
public void testSchedule() throws Exception {
mockMvc.perform(post("/schedule").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andDo(print());
}
}
这是被测控制器:
@RequestMapping(value = "/schedule", method = RequestMethod.POST)
@PreAuthorize("#oauth2.hasScope('dataLicense')")
public int schedule() {
return 0;
}
Spring Boot 1.5 像 @WebMvcTest
一样引入了 test slices。使用这些测试切片并手动加载 OAuth2AutoConfiguration
可以减少测试的样板文件,并且它们 运行 比建议的基于 @SpringBootTest
的解决方案更快。如果您还导入生产安全配置,则可以测试配置的过滤器链是否适用于您的 Web 服务。
以下是设置以及一些您可能会发现有用的其他 classes:
控制器:
@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());
}
}
默认测试配置:
@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";
}
Factory class 用于处理自定义注释:
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
。
还有一种我认为更简洁、更有意义的替代方法。
该方法是自动装配令牌存储,然后添加一个测试令牌,然后供其余客户端使用。
实例测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerIT {
@Autowired
private TestRestTemplate testRestTemplate;
@Autowired
private TokenStore tokenStore;
@Before
public void setUp() {
final OAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
final ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_CLIENT");
final OAuth2Authentication authentication = new OAuth2Authentication(
new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), null);
tokenStore.storeAccessToken(token, authentication);
}
@Test
public void testGivenPathUsersWhenGettingForEntityThenStatusCodeIsOk() {
final HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer FOO");
headers.setContentType(MediaType.APPLICATION_JSON);
// Given Path Users
final UriComponentsBuilder uri = UriComponentsBuilder.fromPath("/api/users");
// When Getting For Entity
final ResponseEntity<String> response = testRestTemplate.exchange(uri.build().toUri(), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
// Then Status Code Is Ok
assertThat(response.getStatusCode(), is(HttpStatus.OK));
}
}
我个人认为对启用了安全性的控制器进行单元测试是不合适的,因为安全性是控制器的一个单独层。我会创建一个集成测试来一起测试所有层。但是,可以轻松修改上述方法以创建使用 MockMvc 的单元测试。
以上代码的灵感来自于 Dave Syer 写的Spring Security test
请注意,此方法适用于与授权服务器共享相同令牌存储的资源服务器。如果您的资源服务器不与授权服务器共享相同的令牌存储,我建议 using wiremock to mock the http responses.
我有另一个解决方案。见下文:
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
@ActiveProfiles("test")
public class AccountContollerTest {
public static Logger log = LoggerFactory.getLogger(AccountContollerTest.class);
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mvc;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private UserRepository users;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomClientDetailsService clientDetialsService;
@Before
public void setUp() {
mvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity(springSecurityFilterChain))
.build();
BaseClientDetails testClient = new ClientBuilder("testclient")
.secret("testclientsecret")
.authorizedGrantTypes("password")
.scopes("read", "write")
.autoApprove(true)
.build();
clientDetialsService.addClient(testClient);
User user = createDefaultUser("testuser", passwordEncoder.encode("testpassword"), "max", "Mustermann", new Email("myemail@test.de"));
users.deleteAll();
users.save(user);
}
@Test
public void shouldRetriveAccountDetailsWithValidAccessToken() throws Exception {
mvc.perform(get("/api/me")
.header("Authorization", "Bearer " + validAccessToken())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.userAuthentication.name").value("testuser"))
.andExpect(jsonPath("$.authorities[0].authority").value("ROLE_USER"));
}
@Test
public void shouldReciveHTTPStatusUnauthenticatedWithoutAuthorizationHeader() throws Exception{
mvc.perform(get("/api/me")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isUnauthorized());
}
private String validAccessToken() throws Exception {
String username = "testuser";
String password = "testpassword";
MockHttpServletResponse response = mvc
.perform(post("/oauth/token")
.header("Authorization", "Basic "
+ new String(Base64Utils.encode(("testclient:testclientsecret")
.getBytes())))
.param("username", username)
.param("password", password)
.param("grant_type", "password"))
.andDo(print())
.andReturn().getResponse();
return new ObjectMapper()
.readValue(response.getContentAsByteArray(), OAuthToken.class)
.accessToken;
}
@JsonIgnoreProperties(ignoreUnknown = true)
private static class OAuthToken {
@JsonProperty("access_token")
public String accessToken;
}
}
希望对您有所帮助!
One more solution I tried to detail enough:-D
它基于设置授权 header,就像上面的一些一样,但我想要:
- 不创建实际有效的 JWT 令牌并使用所有 JWT 身份验证堆栈(单元测试...)
- 测试身份验证以包含 test-case 定义的范围和权限
所以我:
- 创建自定义注释以设置 per-test
OAuth2Authentication
:@WithMockOAuth2Client
(直接客户端连接)和 @WithMockOAuth2User
(代表最终用户的客户端 =>包括我的自定义 @WithMockOAuth2Client 和 Spring @WithMockUser)
- @MockBean TokenStore return 使用上述自定义注释配置的 OAuth2Authentication
- 提供
MockHttpServletRequestBuilder
设置特定授权的工厂 header 被 TokenStore 模拟拦截以注入预期的身份验证。
测试结果:
@WebMvcTest(MyController.class) // Controller to unit-test
@Import(WebSecurityConfig.class) // your class extending WebSecurityConfigurerAdapter
public class MyControllerTest extends OAuth2ControllerTest {
@Test
public void testWithUnauthenticatedClient() throws Exception {
api.post(payload, "/endpoint")
.andExpect(...);
}
@Test
@WithMockOAuth2Client
public void testWithDefaultClient() throws Exception {
api.get("/endpoint")
.andExpect(...);
}
@Test
@WithMockOAuth2User
public void testWithDefaultClientOnBehalfDefaultUser() throws Exception {
MockHttpServletRequestBuilder req = api.postRequestBuilder(null, "/uaa/refresh")
.header("refresh_token", JWT_REFRESH_TOKEN);
api.perform(req)
.andExpect(status().isOk())
.andExpect(...)
}
@Test
@WithMockOAuth2User(
client = @WithMockOAuth2Client(
clientId = "custom-client",
scope = {"custom-scope", "other-scope"},
authorities = {"custom-authority", "ROLE_CUSTOM_CLIENT"}),
user = @WithMockUser(
username = "custom-username",
authorities = {"custom-user-authority"}))
public void testWithCustomClientOnBehalfCustomUser() throws Exception {
api.get(MediaType.APPLICATION_ATOM_XML, "/endpoint")
.andExpect(status().isOk())
.andExpect(xpath(...));
}
}
我找到了一种使用任何令牌存储来测试 spring 安全资源服务器的简单快捷的方法。我的示例 @EnabledResourceServer
使用 jwt 令牌存储。
这里的神奇之处在于我在集成测试中用 InMemoryTokenStore
替换了 JwtTokenStore
。
@RunWith (SpringRunner.class)
@SpringBootTest (classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles ("test")
@TestPropertySource (locations = "classpath:application.yml")
@Transactional
public class ResourceServerIntegrationTest {
@Autowired
private TokenStore tokenStore;
@Autowired
private ObjectMapper jacksonObjectMapper;
@LocalServerPort
int port;
@Configuration
protected static class PrepareTokenStore {
@Bean
@Primary
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
private OAuth2AccessToken token;
private OAuth2Authentication authentication;
@Before
public void init() {
RestAssured.port = port;
token = new DefaultOAuth2AccessToken("FOO");
ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_READER,ROLE_CLIENT");
// Authorities
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_READER"));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("writer", "writer", authorities);
authentication = new OAuth2Authentication(new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), authenticationToken);
tokenStore.storeAccessToken(token, authentication);
}
@Test
public void gbsUserController_findById() throws Exception {
RestAssured.given().log().all().when().headers("Authorization", "Bearer FOO").get("/gbsusers/{id}", 2L).then().log().all().statusCode(HttpStatus.OK.value());
}
我试过很多方法。但我的解决方案比其他人更容易。我在 spring 引导应用程序中使用 OAuth2 JWT 身份验证。我的目标是进行合同测试。我正在用 groovy 编写脚本,合同插件会为我生成测试代码。因此,我不能干扰代码。我有一个简单的 BaseTest class。我需要在此 class 中进行所有必要的配置。这个解决方案对我有用。
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>
导入的插件:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<extensions>true</extensions>
<configuration>
<baseClassForTests>com.test.services.BaseTestClass
</baseClassForTests>
</configuration>
</plugin>
BaseTestClass.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
@ContextConfiguration
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
public class BaseTestClass {
@Autowired
private MyController myController;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(myController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
}
myFirstScenario.groovy(包:/test/resources/contracts”):
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return ok"
request {
method GET()
url("/api/contract/test") {
headers {
header("Authorization","Bearer FOO")
}
}
}
response {
status 200
}
}
MyController.java:
@RestController
@RequestMapping(value = "/api/contract")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class MyController {
...
}
如果您想针对非管理员用户进行测试,您可以使用:
@WithMockUser(username = "admin", roles = {"USER"})
在发布 Spring Security 4 之后 improved support for testing 我想更新我当前的 Spring security oauth2 资源服务器测试。
目前我有一个助手 class,它使用 ResourceOwnerPasswordResourceDetails
设置一个 OAuth2RestTemplate
并测试 ClientId
连接到一个实际的 AccessTokenUri
请求我的测试的有效令牌。然后使用此 resttemplate 在我的 @WebIntegrationTest
s.
我想放弃对实际 AuthorizationServer 的依赖,并在我的测试中使用有效(如果有限)用户凭据,方法是利用 Spring Security 4 中的新测试支持。
到目前为止,我尝试使用 @WithMockUser
、@WithSecurityContext
、SecurityMockMvcConfigurers.springSecurity()
和 SecurityMockMvcRequestPostProcessors.*
的所有尝试都未能通过 MockMvc
进行经过身份验证的调用,并且我在 Spring 示例项目中找不到任何此类工作示例。
任何人都可以帮助我使用某种模拟凭据测试我的 oauth2 资源服务器,同时仍然测试强加的安全限制吗?
** 编辑 ** 此处提供示例代码:https://github.com/timtebeek/resource-server-testing 对于每个测试 classes,我理解为什么它不能正常工作,但我正在寻找可以让我轻松测试安全设置的方法。
我现在正在考虑在 src/test/java
下创建一个非常宽松的 OAuthServer,这可能会有所帮助。有没有人有任何其他建议?
好的,我还无法使用新的 @WithMockUser
或相关注释测试我的独立 oauth2 JWT 令牌保护资源服务器。
作为解决方法,我已经能够通过设置 a permissive AuthorizationServer
under src/test/java, and having that define two clients I use through a helper class 来集成测试我的资源服务器安全性。这为我提供了一些方法,但它还没有我想测试各种用户、角色、范围等那么容易。
我猜从这里开始应该更容易实现我自己的 WithSecurityContextFactory
来创建 OAuth2Authentication
,而不是通常的 UsernamePasswordAuthentication
。但是,我还没有弄清楚如何轻松设置它的细节。欢迎任何关于如何设置的意见或建议。
为了有效地测试资源服务器的安全性,使用 MockMvc
和 RestTemplate
它有助于在 src/test/java
:
AuthorizationServer
授权服务器
@Configuration
@EnableAuthorizationServer
@SuppressWarnings("static-method")
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
public JwtAccessTokenConverter accessTokenConverter() throws Exception {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(SecurityConfig.key("rsa"));
jwt.setVerifierKey(SecurityConfig.key("rsa.pub"));
jwt.afterPropertiesSet();
return jwt;
}
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("myclientwith")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes("myscope")
.and()
.withClient("myclientwithout")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes(UUID.randomUUID().toString());
}
}
集成测试
对于集成测试,可以简单地使用内置的 OAuth2 测试支持规则和注释:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebIntegrationTest(randomPort = true)
@OAuth2ContextConfiguration(MyDetails.class)
public class MyControllerIT implements RestTemplateHolder {
@Value("http://localhost:${local.server.port}")
@Getter
String host;
@Getter
@Setter
RestOperations restTemplate = new TestRestTemplate();
@Rule
public OAuth2ContextSetup context = OAuth2ContextSetup.standard(this);
@Test
public void testHelloOAuth2WithRole() {
ResponseEntity<String> entity = getRestTemplate().getForEntity(host + "/hello", String.class);
assertTrue(entity.getStatusCode().is2xxSuccessful());
}
}
class MyDetails extends ResourceOwnerPasswordResourceDetails {
public MyDetails(final Object obj) {
MyControllerIT it = (MyControllerIT) obj;
setAccessTokenUri(it.getHost() + "/oauth/token");
setClientId("myclientwith");
setUsername("user");
setPassword("password");
}
}
MockMvc 测试
也可以使用 MockMvc
进行测试,但需要一个小帮手 class 来获得一个 RequestPostProcessor
来设置 Authorization: Bearer <token>
header 请求:
@Component
public class OAuthHelper {
// For use with MockMvc
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
AuthorizationServerTokenServices tokenservice;
OAuth2AccessToken createAccessToken(final String clientId) {
// Look up authorities, resourceIds and scopes based on clientId
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
// Default values for other parameters
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
// Create request
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes,
resourceIds, redirectUrl, responseTypes, extensionProperties);
// Create OAuth2AccessToken
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
}
然后您的 MockMvc
测试必须从 OauthHelper
class 获得 RequestPostProcessor
并在发出请求时通过它:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebAppConfiguration
public class MyControllerTest {
@Autowired
private WebApplicationContext webapp;
private MockMvc mvc;
@Before
public void before() {
mvc = MockMvcBuilders.webAppContextSetup(webapp)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
@Autowired
private OAuthHelper helper;
@Test
public void testHelloWithRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwith");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isOk());
}
@Test
public void testHelloWithoutRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwithout");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isForbidden());
}
}
GitHub 上提供了完整的示例项目:
https://github.com/timtebeek/resource-server-testing
我找到了一种更简单的方法来按照我在此处阅读的说明进行操作:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext。此解决方案特定于使用 #oauth2.hasScope
测试 @PreAuthorize
,但我相信它也可以适用于其他情况。
我创建了一个可以应用于 @Test
s 的注释:
WithMockOAuth2Scope
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public @interface WithMockOAuth2Scope {
String scope() default "";
}
WithMockOAuth2ScopeSecurityContextFactory
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import java.util.HashSet;
import java.util.Set;
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
@Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Set<String> scope = new HashSet<>();
scope.add(mockOAuth2Scope.scope());
OAuth2Request request = new OAuth2Request(null, null, null, true, scope, null, null, null, null);
Authentication auth = new OAuth2Authentication(request, null);
context.setAuthentication(auth);
return context;
}
}
示例测试使用 MockMvc
:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class LoadScheduleControllerTest {
private MockMvc mockMvc;
@Autowired
LoadScheduleController loadScheduleController;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(loadScheduleController)
.build();
}
@Test
@WithMockOAuth2Scope(scope = "dataLicense")
public void testSchedule() throws Exception {
mockMvc.perform(post("/schedule").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andDo(print());
}
}
这是被测控制器:
@RequestMapping(value = "/schedule", method = RequestMethod.POST)
@PreAuthorize("#oauth2.hasScope('dataLicense')")
public int schedule() {
return 0;
}
Spring Boot 1.5 像 @WebMvcTest
一样引入了 test slices。使用这些测试切片并手动加载 OAuth2AutoConfiguration
可以减少测试的样板文件,并且它们 运行 比建议的基于 @SpringBootTest
的解决方案更快。如果您还导入生产安全配置,则可以测试配置的过滤器链是否适用于您的 Web 服务。
以下是设置以及一些您可能会发现有用的其他 classes:
控制器:
@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());
}
}
默认测试配置:
@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";
}
Factory class 用于处理自定义注释:
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
。
还有一种我认为更简洁、更有意义的替代方法。
该方法是自动装配令牌存储,然后添加一个测试令牌,然后供其余客户端使用。
实例测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerIT {
@Autowired
private TestRestTemplate testRestTemplate;
@Autowired
private TokenStore tokenStore;
@Before
public void setUp() {
final OAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
final ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_CLIENT");
final OAuth2Authentication authentication = new OAuth2Authentication(
new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), null);
tokenStore.storeAccessToken(token, authentication);
}
@Test
public void testGivenPathUsersWhenGettingForEntityThenStatusCodeIsOk() {
final HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer FOO");
headers.setContentType(MediaType.APPLICATION_JSON);
// Given Path Users
final UriComponentsBuilder uri = UriComponentsBuilder.fromPath("/api/users");
// When Getting For Entity
final ResponseEntity<String> response = testRestTemplate.exchange(uri.build().toUri(), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
// Then Status Code Is Ok
assertThat(response.getStatusCode(), is(HttpStatus.OK));
}
}
我个人认为对启用了安全性的控制器进行单元测试是不合适的,因为安全性是控制器的一个单独层。我会创建一个集成测试来一起测试所有层。但是,可以轻松修改上述方法以创建使用 MockMvc 的单元测试。
以上代码的灵感来自于 Dave Syer 写的Spring Security test
请注意,此方法适用于与授权服务器共享相同令牌存储的资源服务器。如果您的资源服务器不与授权服务器共享相同的令牌存储,我建议 using wiremock to mock the http responses.
我有另一个解决方案。见下文:
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
@ActiveProfiles("test")
public class AccountContollerTest {
public static Logger log = LoggerFactory.getLogger(AccountContollerTest.class);
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mvc;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private UserRepository users;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomClientDetailsService clientDetialsService;
@Before
public void setUp() {
mvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity(springSecurityFilterChain))
.build();
BaseClientDetails testClient = new ClientBuilder("testclient")
.secret("testclientsecret")
.authorizedGrantTypes("password")
.scopes("read", "write")
.autoApprove(true)
.build();
clientDetialsService.addClient(testClient);
User user = createDefaultUser("testuser", passwordEncoder.encode("testpassword"), "max", "Mustermann", new Email("myemail@test.de"));
users.deleteAll();
users.save(user);
}
@Test
public void shouldRetriveAccountDetailsWithValidAccessToken() throws Exception {
mvc.perform(get("/api/me")
.header("Authorization", "Bearer " + validAccessToken())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.userAuthentication.name").value("testuser"))
.andExpect(jsonPath("$.authorities[0].authority").value("ROLE_USER"));
}
@Test
public void shouldReciveHTTPStatusUnauthenticatedWithoutAuthorizationHeader() throws Exception{
mvc.perform(get("/api/me")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isUnauthorized());
}
private String validAccessToken() throws Exception {
String username = "testuser";
String password = "testpassword";
MockHttpServletResponse response = mvc
.perform(post("/oauth/token")
.header("Authorization", "Basic "
+ new String(Base64Utils.encode(("testclient:testclientsecret")
.getBytes())))
.param("username", username)
.param("password", password)
.param("grant_type", "password"))
.andDo(print())
.andReturn().getResponse();
return new ObjectMapper()
.readValue(response.getContentAsByteArray(), OAuthToken.class)
.accessToken;
}
@JsonIgnoreProperties(ignoreUnknown = true)
private static class OAuthToken {
@JsonProperty("access_token")
public String accessToken;
}
}
希望对您有所帮助!
One more solution I tried to detail enough:-D
它基于设置授权 header,就像上面的一些一样,但我想要:
- 不创建实际有效的 JWT 令牌并使用所有 JWT 身份验证堆栈(单元测试...)
- 测试身份验证以包含 test-case 定义的范围和权限
所以我:
- 创建自定义注释以设置 per-test
OAuth2Authentication
:@WithMockOAuth2Client
(直接客户端连接)和@WithMockOAuth2User
(代表最终用户的客户端 =>包括我的自定义 @WithMockOAuth2Client 和 Spring @WithMockUser) - @MockBean TokenStore return 使用上述自定义注释配置的 OAuth2Authentication
- 提供
MockHttpServletRequestBuilder
设置特定授权的工厂 header 被 TokenStore 模拟拦截以注入预期的身份验证。
测试结果:
@WebMvcTest(MyController.class) // Controller to unit-test
@Import(WebSecurityConfig.class) // your class extending WebSecurityConfigurerAdapter
public class MyControllerTest extends OAuth2ControllerTest {
@Test
public void testWithUnauthenticatedClient() throws Exception {
api.post(payload, "/endpoint")
.andExpect(...);
}
@Test
@WithMockOAuth2Client
public void testWithDefaultClient() throws Exception {
api.get("/endpoint")
.andExpect(...);
}
@Test
@WithMockOAuth2User
public void testWithDefaultClientOnBehalfDefaultUser() throws Exception {
MockHttpServletRequestBuilder req = api.postRequestBuilder(null, "/uaa/refresh")
.header("refresh_token", JWT_REFRESH_TOKEN);
api.perform(req)
.andExpect(status().isOk())
.andExpect(...)
}
@Test
@WithMockOAuth2User(
client = @WithMockOAuth2Client(
clientId = "custom-client",
scope = {"custom-scope", "other-scope"},
authorities = {"custom-authority", "ROLE_CUSTOM_CLIENT"}),
user = @WithMockUser(
username = "custom-username",
authorities = {"custom-user-authority"}))
public void testWithCustomClientOnBehalfCustomUser() throws Exception {
api.get(MediaType.APPLICATION_ATOM_XML, "/endpoint")
.andExpect(status().isOk())
.andExpect(xpath(...));
}
}
我找到了一种使用任何令牌存储来测试 spring 安全资源服务器的简单快捷的方法。我的示例 @EnabledResourceServer
使用 jwt 令牌存储。
这里的神奇之处在于我在集成测试中用 InMemoryTokenStore
替换了 JwtTokenStore
。
@RunWith (SpringRunner.class)
@SpringBootTest (classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles ("test")
@TestPropertySource (locations = "classpath:application.yml")
@Transactional
public class ResourceServerIntegrationTest {
@Autowired
private TokenStore tokenStore;
@Autowired
private ObjectMapper jacksonObjectMapper;
@LocalServerPort
int port;
@Configuration
protected static class PrepareTokenStore {
@Bean
@Primary
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
private OAuth2AccessToken token;
private OAuth2Authentication authentication;
@Before
public void init() {
RestAssured.port = port;
token = new DefaultOAuth2AccessToken("FOO");
ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_READER,ROLE_CLIENT");
// Authorities
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_READER"));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("writer", "writer", authorities);
authentication = new OAuth2Authentication(new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), authenticationToken);
tokenStore.storeAccessToken(token, authentication);
}
@Test
public void gbsUserController_findById() throws Exception {
RestAssured.given().log().all().when().headers("Authorization", "Bearer FOO").get("/gbsusers/{id}", 2L).then().log().all().statusCode(HttpStatus.OK.value());
}
我试过很多方法。但我的解决方案比其他人更容易。我在 spring 引导应用程序中使用 OAuth2 JWT 身份验证。我的目标是进行合同测试。我正在用 groovy 编写脚本,合同插件会为我生成测试代码。因此,我不能干扰代码。我有一个简单的 BaseTest class。我需要在此 class 中进行所有必要的配置。这个解决方案对我有用。
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>
导入的插件:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<extensions>true</extensions>
<configuration>
<baseClassForTests>com.test.services.BaseTestClass
</baseClassForTests>
</configuration>
</plugin>
BaseTestClass.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
@ContextConfiguration
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
public class BaseTestClass {
@Autowired
private MyController myController;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(myController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
}
myFirstScenario.groovy(包:/test/resources/contracts”):
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return ok"
request {
method GET()
url("/api/contract/test") {
headers {
header("Authorization","Bearer FOO")
}
}
}
response {
status 200
}
}
MyController.java:
@RestController
@RequestMapping(value = "/api/contract")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class MyController {
...
}
如果您想针对非管理员用户进行测试,您可以使用:
@WithMockUser(username = "admin", roles = {"USER"})