Java 如何测试spring-security-oauth2资源服务器的安全性?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29510759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to test spring-security-oauth2 resource server security?
提问by Tim
Following the release of Spring Security 4 and it's improved support for testingI've wanted to update my current Spring security oauth2 resource server tests.
在 Spring Security 4 发布之后,它改进了对测试的支持,我想更新我当前的 Spring 安全 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. This resttemplate is then used to make requests in my @WebIntegrationTest
s.
目前我有一个助手类,它设置了一个OAuth2RestTemplate
usingResourceOwnerPasswordResourceDetails
和一个ClientId
连接到实际的测试,AccessTokenUri
为我的测试请求一个有效的令牌。然后使用这个 resttemplate 在我的@WebIntegrationTest
s 中发出请求。
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
,@WithSecurityContext
,SecurityMockMvcConfigurers.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-testingFor 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. Does anyone have any other suggestions?
我现在正在考虑在 下创建一个非常宽松的 OAuthServer src/test/java
,这可能会有所帮助。有没有人有其他建议?
采纳答案by Tim
To test resource server security effectively, both with MockMvc
and a RestTemplate
it helps to configure an AuthorizationServer
under src/test/java
:
为了有效地测试资源服务器的安全性,使用MockMvc
和 aRestTemplate
它有助于配置一个AuthorizationServer
under 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());
}
}
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
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 test
测试MockMvc
也是可能的,但需要一个小助手类来获取一个在请求上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
然后,您的测试必须RequestPostProcessor
从OauthHelper
类中获取 a并在发出请求时通过它:
@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:
https://github.com/timtebeek/resource-server-testing
GitHub 上提供了完整的示例项目:https:
//github.com/timtebeek/resource-server-testing
回答by Tim
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. 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.
作为一种解决方法,我已经能够通过在 src/test/java 下设置一个许可AuthorizationServer
来集成测试我的资源服务器安全性,并通过帮助类定义我使用的两个客户端。这让我了解了一些方法,但还没有我想要测试各种用户、角色、范围等那么容易。
I'm guessing from here on it should be easier to implement my own WithSecurityContextFactory
that creates an OAuth2Authentication
, instead of the usual 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.
我猜从这里开始应该更容易实现我自己WithSecurityContextFactory
的OAuth2Authentication
,而不是通常的UsernamePasswordAuthentication
. 但是,我还没有弄清楚如何轻松设置它的细节。欢迎任何关于如何设置的意见或建议。
回答by mclaassen
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. This solution is specific to testing @PreAuthorize
with #oauth2.hasScope
but I'm sure it could be adapted for other situations as well.
我找到了一种更简单的方法来执行这里操作:http: //docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext。此解决方案特定于测试@PreAuthorize
,#oauth2.hasScope
但我相信它也可以适用于其他情况。
I create an annotation which can be applied to @Test
s:
我创建了一个可以应用于@Test
s的注释:
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;
}
回答by gogstad
Spring Boot 1.5 introduced test sliceslike @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. If you also import your production security configuration, you can test that the configured filter chains is working for your web services.
春天开机1.5引入了测试片一样@WebMvcTest
。使用这些测试切片并手动加载可以OAuth2AutoConfiguration
为您的测试提供更少的样板文件,并且它们的运行速度会比建议的@SpringBootTest
基于解决方案的速度更快。如果您还导入了生产安全配置,则可以测试配置的过滤器链是否适用于您的 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 HymansonTester<Booking> json;
@MockBean
private BookingRepository bookingRepository;
@MockBean
public ResourceServerTokenServices resourceServerTokenServices;
@Before
public void setUp() throws Exception {
// Stub the remote call that loads the authentication object
when(resourceServerTokenServices.loadAuthentication(anyString())).thenAnswer(invocation -> SecurityContextHolder.getContext().getAuthentication());
}
@Test
@WithOAuthSubject(scopes = {"myapi:read", "myapi:write"})
public void mustHaveValidBookingForPatch() throws Exception {
mvc.perform(patch(API_URL)
.header(AUTHORIZATION, "Bearer foo")
.content(json.write(new Booking("myguid", "aes")).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is2xxSuccessful());
}
}
DefaultTestConfiguration:
默认测试配置:
@TestConfiguration
@Import({MySecurityConfig.class, OAuth2AutoConfiguration.class})
public class DefaultTestConfiguration {
}
MySecurityConfig(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
. 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.
我使用来自我们身份服务器的响应副本来创建真实的OAuth2Authentication
. 您可能只需复制我的代码即可。如果您想为您的身份服务器重复该过程,请在org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication
或 中放置一个断点org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication
,具体取决于您是否已配置自定义ResourceServerTokenServices
。
回答by Thomas Turrell-Croft
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 testwritten 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 响应。
回答by Rocks360
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("[email protected]"));
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!
希望它会有所帮助!
回答by ch4mp
One more solution I tried to detail enough:-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...)
- Test authentication to contain test-case defined scopes and authorities
- 不创建实际有效的 JWT 令牌并使用所有 JWT 身份验证堆栈(单元测试...)
- 测试身份验证以包含测试用例定义的范围和权限
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) - @MockBean the TokenStore to return the OAuth2Authentication configured with above custom annotations
- provide
MockHttpServletRequestBuilder
factories that set a specific Authorization header intercepted by TokenStore mock to inject expected authentication.
- 创建了自定义注释来设置每个测试
OAuth2Authentication
:(@WithMockOAuth2Client
直接客户端连接)&@WithMockOAuth2User
(代表最终用户的客户端 => 包括我的自定义 @WithMockOAuth2Client 和 Spring @WithMockUser) - @MockBean TokenStore 返回使用上述自定义注解配置的 OAuth2Authentication
- 提供
MockHttpServletRequestBuilder
设置由 TokenStore 模拟拦截的特定 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(...));
}
}
回答by talipkorkmaz
I found an easy and rapid way for testing spring security resource server with any token store. Im my example @EnabledResourceServer
uses jwt token store.
我找到了一种使用任何令牌存储测试 spring 安全资源服务器的简单快捷的方法。我的示例@EnabledResourceServer
使用 jwt 令牌存储。
The magic here is I replaced JwtTokenStore
with InMemoryTokenStore
at integration test.
这里的神奇之处在于我在集成测试中替换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 HymansonObjectMapper;
@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());
}
回答by Bilal Demir
I've tried many ways. But my solution is easier than others. I'm using OAuth2 JWT authentication in my spring boot application. My goal is to do a contract test. I'm writing a script with groovy and the contract plugin generates test codes for me. Therefore, I cannot interfere with the codes. I have a simple BaseTest class. I need to do all the necessary configurations in this class. This solution worked for me.
我尝试了很多方法。但是我的解决方案比其他解决方案更容易。我在 Spring Boot 应用程序中使用 OAuth2 JWT 身份验证。我的目标是进行合同测试。我正在用 groovy 编写脚本,合同插件为我生成测试代码。因此,我不能干扰代码。我有一个简单的 BaseTest 类。我需要在这个类中做所有必要的配置。这个解决方案对我有用。
Imported dependency:
导入依赖:
<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>
Imported Plugins:
导入插件:
<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
基础测试类.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 (package:"/test/resources/contracts"):
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:
我的控制器.java:
@RestController
@RequestMapping(value = "/api/contract")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class MyController {
...
}
if you want to test for non-admin users you can use:
如果要测试非管理员用户,可以使用:
@WithMockUser(username = "admin", roles = {"USER"})