【问题标题】:How to test spring-security-oauth2 resource server security?如何测试 spring-security-oauth2 资源服务器安全性?
【发布时间】:2015-06-13 04:21:19
【问题描述】:

随着 Spring Security 4 的发布,它是 improved support for testing,我想更新我当前的 Spring security oauth2 资源服务器测试。

目前我有一个助手类,它使用ResourceOwnerPasswordResourceDetails 设置OAuth2RestTemplate 和一个测试ClientId,连接到一个实际的AccessTokenUri,为我的测试请求一个有效的令牌。然后使用这个 resttemplate 在我的@WebIntegrationTests 中发出请求。

我想通过利用 Spring Security 4 中的新测试支持,放弃对实际 AuthorizationServer 的依赖,以及在我的测试中使用有效(如果有限)用户凭据。

到目前为止,我使用@WithMockUser@WithSecurityContextSecurityMockMvcConfigurers.springSecurity()SecurityMockMvcRequestPostProcessors.* 的所有尝试都未能通过MockMvc 进行经过身份验证的调用,并且我在 Spring 示例中找不到任何此类工作示例项目。

谁能帮助我使用某种模拟凭据测试我的 oauth2 资源服务器,同时仍然测试施加的安全限制?

** 编辑 ** 此处提供示例代码:https://github.com/timtebeek/resource-server-testing 对于每个测试类,我都理解它为什么不能正常工作,但我正在寻找可以让我轻松测试安全设置的方​​法。

我现在正在考虑在src/test/java 下创建一个非常宽松的 OAuthServer,这可能会有所帮助。有人有其他建议吗?

【问题讨论】:

  • 您能否举例说明您的一项测试是什么样的?您只是在测试基于方法的安全性吗?你在使用 MockMvc 吗?您是否正在对您的服务进行实际的 REST 调用?
  • @RobWinch 我已经使用每种方法添加了示例代码,并了解它为什么不起作用。我正在寻找在测试安全方面的同时可行的方法。
  • 感谢蒂姆,提供所有代码。运行 testHelloUser#MyControllerIT.java 时,我似乎得到 401。你能帮我解决这个问题吗?
  • 是对我的存储库进行了干净的签出,还是您将部分复制到您自己的代码库中?我这个月要出差,所以无法访问 PC 来测试任何东西。抱歉!
  • 感谢您的快速回复。我克隆了 git repo 并运行了 OOB 测试。我将采用您的原始解决方案,即目前拥有外部授权服务器。但我对你的最终解决方案非常感兴趣,因为它更干净。有空的时候你介意看看吗?

标签: java spring-security spring-security-oauth2


【解决方案1】:

为了有效地测试资源服务器的安全性,使用MockMvcRestTemplate 有助于在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 进行测试,但需要一个小助手类来获取在请求中设置Authorization: Bearer &lt;token&gt; 标头的RequestPostProcessor

@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 类中获取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

【讨论】:

  • 使用 TestingAuthenticationToken(github 示例)和 UsernamePasswordAuthenticationToken(此处发布的示例)有什么区别?似乎该代码适用于 Authentication 接口的任何实现......我错过了什么?
  • 猜你指的是我之前在 GitHub 上使用的TestingAuthenticationToken:没有真正需要使用那个或UsernamePasswordAuthenticationToken;这只是我从ClientDetailsService 中提取详细信息值所做的更改的一部分。你用以前的版本没问题,但我从现在开始用这个。
  • 这工作正常,但 UsernamePasswordAuthenticationToken authenticationToken 中的权限应该是用户的权限,而不是客户端的权限。
  • “mockMvc”解决方案完美运行,确实在我们的应用程序中进行了一些特定的调整。这行.apply(springSecurity()) 对于向集成测试添加正确的安全上下文非常重要。但就个人而言,oAuthHelper 类不是必需的,因为您可以在具有特定角色的模拟用户中模拟安全细节,例如:)
【解决方案2】:

按照我在此处阅读的说明,我找到了一种更简单的方法:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext。此解决方案专门用于测试 @PreAuthorize#oauth2.hasScope,但我相信它也可以适用于其他情况。

我创建了一个可以应用于@Tests 的注解:

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

【讨论】:

  • 有趣的方法!可能使我不必设置 AuthorizationServer 和获取测试令牌。我在调整您的示例以使用 OAuth2Authentication 中的特定用户时遇到了麻烦。我的安全模型主要基于您的身份,而不是您的令牌范围。关于如何调整您的样本以支持这一点的任何建议?
  • @Tim 您应该能够将安全上下文中的Authentication 设置为任意身份验证对象。我认为这里的关键区别可能是您尝试使用真实的 OAuth2RestTemplate 发送请求,而我在测试中所做的是使用 mockMvc 发送请求。
  • 谢谢!终于能够更清楚地看到这一点,并相应地更新了我的示例项目:github.com/timtebeek/resource-server-testing/pull/1 这两种方法现在都有效,但用途不同。对于基于用户名/范围的访问规则,我推荐您的方法;就我而言,我解码访问令牌并根据其中的属性拥有多租户访问规则;那确实需要一个实际的令牌。 :)
  • 我能够成功地使用一个完整的假用户详细信息并继续我的测试。
  • 如果有人对模拟令牌值感兴趣,您可以在OAuth2AuthenticationDetails 中设置details,并通过属性httpServletrequest 和“承载”传递OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUEOAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE令牌值。之后,您可以使用((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getTokenValue() 访问您应用中的令牌值
【解决方案3】:

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

默认测试配置

@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#loadAuthenticationorg.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication 中放置一个断点,具体取决于您是否配置了自定义ResourceServerTokenServices

【讨论】:

  • 哇,感谢您努力提出一种全新的测试方法,正如您所说,这可能更快,并且不会设置应用程序上下文中不需要的部分。很酷! :)
  • 我已经尝试过您的解决方案,但是在构建测试请求时忘记添加身份验证标头,当然,它不起作用:/。也许可以更加强调将这个 Authorization 标头添加到每个涉及安全的请求的必要性?
【解决方案4】:

还有我认为更清洁、更有意义的替代方法。

方法是自动装配令牌存储,然后添加一个测试令牌,然后其他客户端可以使用该令牌。

示例测试

@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

【讨论】:

    【解决方案5】:

    我对此有另一种解决方案。见下文:

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

    希望对您有所帮助!

    【讨论】:

      【解决方案6】:

      好的,我还不能使用新的 @WithMockUser 或相关注释测试我的独立 oauth2 JWT 令牌保护资源服务器。

      作为一种解决方法,我已经能够通过设置a permissive AuthorizationServer under src/test/java 来集成测试我的资源服务器的安全性,并让它定义两个我使用through a helper class 的客户端。这让我有了一些了解,但它还没有我想测试各种用户、角色、范围等那么容易。

      我猜从这里开始应该更容易实现我自己的WithSecurityContextFactory,它创建一个OAuth2Authentication,而不是通常的UsernamePasswordAuthentication。但是,我还没有弄清楚如何轻松设置它的细节。欢迎任何有关如何设置的 cmets 或建议。

      【讨论】:

        【解决方案7】:

        我找到了一种使用任何令牌存储测试 Spring 安全资源服务器的简单快速的方法。我的示例 @EnabledResourceServer使用 jwt 令牌存储。

        这里的神奇之处在于我在集成测试中将JwtTokenStore 替换为InMemoryTokenStore

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

        【讨论】:

          【解决方案8】:

          One more solution I tried to detail enough:-D

          它基于设置一个授权标头,就像上面的一些,但我想要:

          • 不创建实际有效的 JWT 令牌并使用所有 JWT 身份验证堆栈(单元测试...)
          • 测试身份验证以包含测试用例定义的范围和权限

          所以我已经:

          • 创建自定义注释来设置每个测试 OAuth2Authentication: @WithMockOAuth2Client(直接客户端连接)和@WithMockOAuth2User(代表最终用户的客户端 => 包括我的自定义 @WithMockOAuth2Client 和 Spring @WithMockUser )
          • @MockBean TokenStore 返回上面自定义注解配置的OAuth2Authentication
          • 提供MockHttpServletRequestBuilder 工厂,这些工厂设置由 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(...));
              }
          }
          

          【讨论】:

          • 再次阅读所有堆栈,我才意识到this one 与我的解决方案有多接近。我已经尝试过,错过了设置标题的行,并从头开始构建了我自己的解决方案。最后,我只是进一步推动了 OAuth2Authentication 配置选项并添加了包装器,以免忘记这个该死的标头。
          【解决方案9】:

          我尝试了很多方法。但我的解决方案比其他人更容易。我在我的 Spring Boot 应用程序中使用 OAuth2 JWT 身份验证。我的目标是进行合同测试。我正在用 groovy 编写脚本,并且合约插件会为我生成测试代码。因此,我不能干涉代码。我有一个简单的 BaseTest 类。我需要在这个类中做所有必要的配置。这个解决方案对我有用。

          导入的依赖:

          <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"})
          

          【讨论】:

            猜你喜欢
            • 2019-11-27
            • 2018-08-17
            • 2013-12-21
            • 2017-03-25
            • 2017-12-15
            • 2018-07-08
            • 2021-10-29
            • 2016-04-22
            • 2021-02-08
            相关资源
            最近更新 更多