简体   繁体   English

如何测试spring-security-oauth2资源服务器安全性?

[英]How to test spring-security-oauth2 resource server security?

Following the release of Spring Security 4 and it's improved support for testing I've wanted to update my current Spring security oauth2 resource server tests. 在Spring Security 4发布之后,它改进了对测试支持,我想更新我当前的Spring security oauth2资源服务器测试。

At present I have a helper class that sets up a OAuth2RestTemplate using ResourceOwnerPasswordResourceDetails with a test ClientId connecting to an actual AccessTokenUri to requests a valid token for my tests. 目前我有一个帮助器类, OAuth2RestTemplate使用ResourceOwnerPasswordResourceDetails设置OAuth2RestTemplate ,测试ClientId连接到实际的AccessTokenUri ,为我的测试请求有效的令牌。 This resttemplate is then used to make requests in my @WebIntegrationTest s. 然后,这个resttemplate用于在我的@WebIntegrationTest发出请求。

I'd like to drop the dependency on the actual AuthorizationServer, and the use of valid (if limited) user credentials in my tests, by taking advantage of the new testing support in Spring Security 4. 我想通过利用Spring Security 4中的新测试支持,放弃对实际AuthorizationServer的依赖,并在我的测试中使用有效(如果有限)用户凭据。

Up to now all my attempts at using @WithMockUser , @WithSecurityContext , SecurityMockMvcConfigurers.springSecurity() & SecurityMockMvcRequestPostProcessors.* have failed to make authenticated calls through MockMvc , and I can not find any such working examples in the Spring example projects. 到目前为止,我使用@WithMockUser@WithSecurityContextSecurityMockMvcConfigurers.springSecurity()SecurityMockMvcRequestPostProcessors.*所有尝试都未能通过MockMvc进行经过身份验证的调用,我在Spring示例项目中找不到任何此类工作示例。

Can anyone help me test my oauth2 resource server with some kind of mocked credentials, while still testing the security restrictions imposed? 任何人都可以帮助我使用某种模拟凭证来测试我的oauth2资源服务器,同时仍然测试所施加的安全限制吗?

** EDIT ** Sample code available here: https://github.com/timtebeek/resource-server-testing For each of the test classes I understand why it won't work as it, but I'm looking for ways that would allow me to test the security setup easily. ** 编辑 **示例代码可在此处获取: https//github.com/timtebeek/resource-server-testing对于每个测试类,我理解为什么它不能正常工作,但我正在寻找方法这将允许我轻松测试安全设置。

I'm now thinking of creating a very permissive OAuthServer under src/test/java , which might help a bit. 我现在正在考虑在src/test/java下创建一个非常宽松的OAuthServer,这可能会有所帮助。 Does anyone have any other suggestions? 有没有人有任何其他建议?

To test resource server security effectively, both with MockMvc and a RestTemplate it helps to configure an AuthorizationServer under src/test/java : 为了有效地测试资源服务器安全性,使用MockMvcRestTemplate它有助于在src/test/java下配置AuthorizationServer

AuthorizationServer 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());
    }
}

Integration test 整合测试
For integration tests one can then simply use built in OAuth2 test support rule and annotions: 对于集成测试,可以简单地使用内置的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 test MockMvc测试
Testing with MockMvc is also possible, but needs a little helper class to get a RequestPostProcessor that sets the Authorization: Bearer <token> header on requests: 使用MockMvc测试也是可能的,但需要一个小帮助类来获取RequestPostProcessor ,以便在RequestPostProcessor中设置Authorization: Bearer <token>标头:

@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);
    }
}

Your MockMvc tests must then get a RequestPostProcessor from the OauthHelper class and pass it when making requests: 然后,您的MockMvc测试必须从OauthHelper类获取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());
    }
}

A full sample project is available on GitHub: GitHub上提供了一个完整的示例项目:
https://github.com/timtebeek/resource-server-testing https://github.com/timtebeek/resource-server-testing

I found a much easier way to do this following directions I read here: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext . 我按照我在此处阅读的说明找到了一种更简单的方法: http//docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext This solution is specific to testing @PreAuthorize with #oauth2.hasScope but I'm sure it could be adapted for other situations as well. 此解决方案特定于使用#oauth2.hasScope测试@PreAuthorize ,但我确信它也可以适用于其他情况。

I create an annotation which can be applied to @Test s: 我创建了一个可以应用于@Test的注释:

WithMockOAuth2Scope 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 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;
    }
}

Example test using MockMvc : 使用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());
    }
}

And this is the controller under test: 这是被测控制器:

@RequestMapping(value = "/schedule", method = RequestMethod.POST)
@PreAuthorize("#oauth2.hasScope('dataLicense')")
public int schedule() {
    return 0;
}

Spring Boot 1.5 introduced test slices like @WebMvcTest . Spring Boot 1.5引入了像@WebMvcTest这样的测试片 Using these test slices and manually load the OAuth2AutoConfiguration gives your tests less boilerplate and they'll run faster than the proposed @SpringBootTest based solutions. 使用这些测试切片并手动加载OAuth2AutoConfiguration使测试更少样板,并且它们比基于@SpringBootTest的解决方案运行得更快。 If you also import your production security configuration, you can test that the configured filter chains is working for your web services. 如果还导入生产安全性配置,则可以测试配置的筛选器链是否适用于Web服务。

Here's the setup along with some additional classes that you'll probably find beneficial: 这里是设置以及一些您可能会发现有益的其他类:

Controller : 控制器

@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);
    }
}

Test : 测试

@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 : DefaultTestConfiguration

@TestConfiguration
@Import({MySecurityConfig.class, OAuth2AutoConfiguration.class})
public class DefaultTestConfiguration {

}

MySecurityConfig (this is for production): 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();
    }

}

Custom annotation for injecting scopes from tests : 用于从测试中注入范围的自定义注释

@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 for handling the custom annotation : 用于处理自定义注释的工厂类

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;
    }
}

I use a copy of the response from our identity server for creating a realistic OAuth2Authentication . 我使用身份服务器的响应副本来创建逼真的OAuth2Authentication You can probably just copy my code. 您可以只复制我的代码。 If you want to repeat the process for your identity server, place a breakpoint in org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication or org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication , depending on whether you have configured a custom ResourceServerTokenServices or not. 如果要为身份服务器重复此过程,请在org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication设置断点org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthenticationorg.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication ,取决于您是否配置了自定义ResourceServerTokenServices

I have another solution for this. 我有另一个解决方案。 See below: 见下文:

@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;
    }
}

Hope it will help! 希望它会有所帮助!

OK, I've not yet been able to test my standalone oauth2 JWT token protected resource-server using the new @WithMockUser or related annotations. 好的,我还没有能够使用新的@WithMockUser或相关注释来测试我的独立的oauth2 JWT令牌受保护的资源服务器。

As a workaround, I have been able to integration test my resource server security by setting up a permissive AuthorizationServer under src/test/java , and having that define two clients I use through a helper class . 作为一种解决方法,我已经能够通过在src / test / java下设置一个允许的AuthorizationServer来集成测试我的资源服务器安全性,并通过一个帮助器类来定义我使用的两个客户端。 This gets me some of the way there, but it's not yet as easy as I'd like to test various users, roles, scopes, etc. 这让我有了一些方法,但它还不像我想测试各种用户,角色,范围等那么容易。

I'm guessing from here on it should be easier to implement my own WithSecurityContextFactory that creates an OAuth2Authentication , instead of the usual UsernamePasswordAuthentication . 我从这里就可以猜测应该更容易实现自己的WithSecurityContextFactory创建一个OAuth2Authentication而不是通常的, UsernamePasswordAuthentication However, I have not yet been able to work out the detail of how to easily set this up. 但是,我还没有弄清楚如何轻松设置它的细节。 Any comments or suggestions how to set this up are welcome. 欢迎提出任何意见或建议如何设置。

There is alternative approach which I believe to be cleaner and more meaningful. 我认为有另一种方法可以更清洁,更有意义。

The approach is to autowire the token store and then add a test token which can then be used by the rest client. 方法是自动装配令牌存储,然后添加一个测试令牌,然后由其他客户端使用。

An example test : 一个示例测试

@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));
    }

}

Personally I believe that it is not appropriate to unit test a controller with security enabled since security is a separate layer to the controller. 就个人而言,我认为单独测试启用了安全性的控制器是不合适的,因为安全性是控制器的一个单独层。 I would create an integration test that tests all of the layers together. 我会创建一个集成测试,测试所有层。 However the above approach can easily be modified to create a Unit Test with that uses MockMvc. 但是,可以轻松修改上述方法以创建使用MockMvc的单元测试。

The above code is inspired by a Spring Security test written by Dave Syer. 上面的代码受到Dave Syer编写的Spring Security测试的启发。

Note this approach is for resource servers that share the same token store as the authorisation server. 请注意,此方法适用于与授权服务器共享相同令牌存储的资源服务器。 If your resource server does not share the same token store as the authorisation server I recommend using wiremock to mock the http responses . 如果您的资源服务器与授权服务器不共享相同的令牌存储,我建议使用wiremock来模拟http响应

I found an easy and rapid way for testing spring security resource server with any token store. 我发现了一种简单快捷的方法,可以使用任何令牌存储来测试Spring安全资源服务器。 Im my example @EnabledResourceServer uses jwt token store. 我的例子@EnabledResourceServer使用jwt令牌存储。

The magic here is I replaced JwtTokenStore with InMemoryTokenStore at integration test. 这里的神奇之处在于我在集成测试中用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());

}

One more solution I tried to detail enough :-D 我试图详细说明的另一个解决方案 :-D

It is based on setting an Authorization header, like some above, but I wanted: 它基于设置Authorization标头,如上所述,但我想:

  • Not to create actually valid JWT tokens and using all JWT authentication stack (unit tests...) 不创建实际有效的JWT令牌并使用所有JWT身份验证堆栈(单元测试...)
  • Test authentication to contain test-case defined scopes and authorities 测试身份验证以包含测试用例定义的范围和权限

So I've: 所以我:

  • created custom annotations to set up a per-test OAuth2Authentication : @WithMockOAuth2Client (direct client connection) & @WithMockOAuth2User (client acting on behalf of an end user => includes both my custom @WithMockOAuth2Client and Spring @WithMockUser) 创建自定义注释以设置每个测试OAuth2Authentication@WithMockOAuth2Client (直接客户端连接)和@WithMockOAuth2User (代表最终用户的客户端=>包括我的自定义@ WithMockOAuth2Client和Spring @WithMockUser)
  • @MockBean the TokenStore to return the OAuth2Authentication configured with above custom annotations @MockBean TokenStore返回使用上述自定义注释配置的OAuth2Authentication
  • provide MockHttpServletRequestBuilder factories that set a specific Authorization header intercepted by TokenStore mock to inject expected authentication. 提供MockHttpServletRequestBuilder工厂,设置由TokenStore mock拦截的特定Authorization头,以注入预期的身份验证。

The result to get you tested: 测试结果:

@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(...));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM