我只是很难让我的控制器单元测试正常工作,因为在我看来,如果使用OAuth,SpringDoc中的内容是不够的。在我的例子中,是Oauth2和JWT。
我尝试使用@with mockuser
,@with userdetails
,甚至使用@with securitycontext
和自定义UserSecurityContextFactory
定义我自己的注释,但在计算安全表达式时,总是在UserSecurityContext中获得匿名用户,无论我在工厂中设置测试上下文的是什么。。。
我提出了我刚刚想到的解决方案,但由于我不确定嘲笑令牌服务是最有效/干净的方法,请随时提供更好的解决方案。
[编辑于2019年5月]
下面的解决方案特定于现在不建议使用的sping-security-oAuth2。
我编写了一个库来实现与Spring5相同的目标,其中一些用于sping-security-test 5.2。他们选择只集成JWT流API,所以如果您需要测试服务(需要使用注释)或使用不透明令牌内省,您可能需要稍微浏览一下我的回购...
[2019年7月编辑]
我现在向maven central发布我的spring 5“spring插件”libs,这大大提高了可用性
源代码和自述文件仍在github上。
[spring-security-oauth2的解决方案]
我迭代到的解决方案是将请求中的虚拟“授权”头与拦截它的模拟令牌服务相结合(如果您查看编辑堆栈,经过多次尝试)。
我在Github上的一个库中提供了完整的帮助源,你可以在那里找到OAuth2控制器测试样本。
简而言之:没有授权标题-
这里有两个例子:
这里已经描述了一种类似的方法,我在拉扯头发几天后就明白了这一点。我只是进一步介绍了针对@WebMvcTest
s的模拟OAuth2身份验证配置和工具。
样本用法
由于这篇文章很长,公开了一个涉及相当多代码的解决方案,让我们从结果开始,这样您就可以决定它是否值得一读;)
@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(...));
}
}
很时髦,不是吗?
P.S.api
是MockMvcHelper
的一个实例,这是我自己为MockMvc
提供的一个包装器,在本文末尾提供。
@使用MockOAuth2Client模拟仅客户端身份验证(不涉及最终用户)
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.class)
public @interface WithMockOAuth2Client {
String clientId() default "web-client";
String[] scope() default {"openid"};
String[] authorities() default {};
boolean approved() default true;
class WithMockOAuth2ClientSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Client> {
public static OAuth2Request getOAuth2Request(final WithMockOAuth2Client annotation) {
final Set<? extends GrantedAuthority> authorities = Stream.of(annotation.authorities())
.map(auth -> new SimpleGrantedAuthority(auth))
.collect(Collectors.toSet());
final Set<String> scope = Stream.of(annotation.scope())
.collect(Collectors.toSet());
return new OAuth2Request(
null,
annotation.clientId(),
authorities,
annotation.approved(),
scope,
null,
null,
null,
null);
}
@Override
public SecurityContext createSecurityContext(final WithMockOAuth2Client annotation) {
final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
ctx.setAuthentication(new OAuth2Authentication(getOAuth2Request(annotation), null));
SecurityContextHolder.setContext(ctx);
return ctx;
}
}
}
以模拟代表最终用户的客户端身份验证
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2User.WithMockOAuth2UserSecurityContextFactory.class)
public @interface WithMockOAuth2User {
WithMockOAuth2Client client() default @WithMockOAuth2Client();
WithMockUser user() default @WithMockUser();
class WithMockOAuth2UserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2User> {
/**
* Sadly, #WithMockUserSecurityContextFactory is not public,
* so re-implement mock user authentication creation
*
* @param user
* @return an Authentication with provided user details
*/
public static UsernamePasswordAuthenticationToken getUserAuthentication(final WithMockUser user) {
final String principal = user.username().isEmpty() ? user.value() : user.username();
final Stream<String> grants = user.authorities().length == 0 ?
Stream.of(user.roles()).map(r -> "ROLE_" + r) :
Stream.of(user.authorities());
final Set<? extends GrantedAuthority> userAuthorities = grants
.map(auth -> new SimpleGrantedAuthority(auth))
.collect(Collectors.toSet());
return new UsernamePasswordAuthenticationToken(
new User(principal, user.password(), userAuthorities),
principal + ":" + user.password(),
userAuthorities);
}
@Override
public SecurityContext createSecurityContext(final WithMockOAuth2User annotation) {
final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
ctx.setAuthentication(new OAuth2Authentication(
WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.getOAuth2Request(annotation.client()),
getUserAuthentication(annotation.user())));
SecurityContextHolder.setContext(ctx);
return ctx;
}
}
}
OAuth2MockMvcHelper帮助构建具有预期授权头的测试请求
html" target="_blank">java prettyprint-override">public class OAuth2MockMvcHelper extends MockMvcHelper {
public static final String VALID_TEST_TOKEN_VALUE = "test.fake.jwt";
public OAuth2MockMvcHelper(
final MockMvc mockMvc,
final ObjectFactory<HttpMessageConverters> messageConverters,
final MediaType defaultMediaType) {
super(mockMvc, messageConverters, defaultMediaType);
}
/**
* Adds OAuth2 support: adds an Authorisation header to all request builders
* if there is an OAuth2Authentication in test security context.
*
* /!\ Make sure your token services recognize this dummy "VALID_TEST_TOKEN_VALUE" token as valid during your tests /!\
*
* @param contentType should be not-null when issuing request with body (POST, PUT, PATCH), null otherwise
* @param accept should be not-null when issuing response with body (GET, POST, OPTION), null otherwise
* @param method
* @param urlTemplate
* @param uriVars
* @return a request builder with minimal info you can tweak further (add headers, cookies, etc.)
*/
@Override
public MockHttpServletRequestBuilder requestBuilder(
Optional<MediaType> contentType,
Optional<MediaType> accept,
HttpMethod method,
String urlTemplate,
Object... uriVars) {
final MockHttpServletRequestBuilder builder = super.requestBuilder(contentType, accept, method, urlTemplate, uriVars);
if (SecurityContextHolder.getContext().getAuthentication() instanceof OAuth2Authentication) {
builder.header("Authorization", "Bearer " + VALID_TEST_TOKEN_VALUE);
}
return builder;
}
}
OAuth2Controller测试控制器单元测试的父级
@RunWith(SpringRunner.class)
@Import(OAuth2MockMvcConfig.class)
public class OAuth2ControllerTest {
@MockBean
private ResourceServerTokenServices tokenService;
@Autowired
protected OAuth2MockMvcHelper api;
@Autowired
protected SerializationHelper conv;
@Before
public void setUpTokenService() {
when(tokenService.loadAuthentication(api.VALID_TEST_TOKEN_VALUE))
.thenAnswer(invocation -> SecurityContextHolder.getContext().getAuthentication());
}
}
@TestConfiguration
class OAuth2MockMvcConfig {
@Bean
public SerializationHelper serializationHelper(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SerializationHelper(messageConverters);
}
@Bean
public OAuth2MockMvcHelper mockMvcHelper(
MockMvc mockMvc,
ObjectFactory<HttpMessageConverters> messageConverters,
@Value("${controllers.default-media-type:application/json;charset=UTF-8}") MediaType defaultMediaType) {
return new OAuth2MockMvcHelper(mockMvc, messageConverters, defaultMediaType);
}
}
/**
* Wraps MockMvc to further ease interaction with tested API:
* provides with:<ul>
* <li>many request shortcuts for simple cases (see get, post, put, patch, delete methods)</li>
* <li>perfom method along with request builder initialisation shortcuts (see getRequestBuilder, etc.) when more control is required (additional headers, ...)</li>
* </ul>
*/
public class MockMvcHelper {
private final MockMvc mockMvc;
private final MediaType defaultMediaType;
protected final SerializationHelper conv;
public MockMvcHelper(MockMvc mockMvc, ObjectFactory<HttpMessageConverters> messageConverters, MediaType defaultMediaType) {
this.mockMvc = mockMvc;
this.conv = new SerializationHelper(messageConverters);
this.defaultMediaType = defaultMediaType;
}
/**
* Generic request builder which adds relevant "Accept" and "Content-Type" headers
*
* @param contentType should be not-null when issuing request with body (POST, PUT, PATCH), null otherwise
* @param accept should be not-null when issuing response with body (GET, POST, OPTION), null otherwise
* @param method
* @param urlTemplate
* @param uriVars
* @return a request builder with minimal info you can tweak further: add headers, cookies, etc.
*/
public MockHttpServletRequestBuilder requestBuilder(
Optional<MediaType> contentType,
Optional<MediaType> accept,
HttpMethod method,
String urlTemplate,
Object... uriVars) {
final MockHttpServletRequestBuilder builder = request(method, urlTemplate, uriVars);
contentType.ifPresent(builder::contentType);
accept.ifPresent(builder::accept);
return builder;
}
public ResultActions perform(MockHttpServletRequestBuilder request) throws Exception {
return mockMvc.perform(request);
}
/* GET */
public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
return requestBuilder(Optional.empty(), Optional.of(accept), HttpMethod.GET, urlTemplate, uriVars);
}
public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) {
return getRequestBuilder(defaultMediaType, urlTemplate, uriVars);
}
public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(getRequestBuilder(accept, urlTemplate, uriVars));
}
public ResultActions get(String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(getRequestBuilder(urlTemplate, uriVars));
}
/* POST */
public <T> MockHttpServletRequestBuilder postRequestBuilder(final T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
return feed(
requestBuilder(Optional.of(contentType), Optional.of(accept), HttpMethod.POST, urlTemplate, uriVars),
payload,
contentType);
}
public <T> MockHttpServletRequestBuilder postRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return postRequestBuilder(payload, defaultMediaType, defaultMediaType, urlTemplate, uriVars);
}
public <T> ResultActions post(final T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars));
}
public <T> ResultActions post(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(postRequestBuilder(payload, urlTemplate, uriVars));
}
/* PUT */
public <T> MockHttpServletRequestBuilder putRequestBuilder(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
return feed(
requestBuilder(Optional.of(contentType), Optional.empty(), HttpMethod.PUT, urlTemplate, uriVars),
payload,
contentType);
}
public <T> MockHttpServletRequestBuilder putRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return putRequestBuilder(payload, defaultMediaType, urlTemplate, uriVars);
}
public <T> ResultActions put(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars));
}
public <T> ResultActions put(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(putRequestBuilder(payload, urlTemplate, uriVars));
}
/* PATCH */
public <T> MockHttpServletRequestBuilder patchRequestBuilder(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
return feed(
requestBuilder(Optional.of(contentType), Optional.empty(), HttpMethod.PATCH, urlTemplate, uriVars),
payload,
contentType);
}
public <T> MockHttpServletRequestBuilder patchRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return patchRequestBuilder(payload, defaultMediaType, urlTemplate, uriVars);
}
public <T> ResultActions patch(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars));
}
public <T> ResultActions patch(final T payload, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(patchRequestBuilder(payload, urlTemplate, uriVars));
}
/* DELETE */
public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) {
return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars);
}
public ResultActions delete(String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(deleteRequestBuilder(urlTemplate, uriVars));
}
/* HEAD */
public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) {
return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars);
}
public ResultActions head(String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(headRequestBuilder(urlTemplate, uriVars));
}
/* OPTION */
public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
return requestBuilder(Optional.empty(), Optional.of(accept), HttpMethod.OPTIONS, urlTemplate, uriVars);
}
public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) {
return requestBuilder(Optional.empty(), Optional.of(defaultMediaType), HttpMethod.OPTIONS, urlTemplate, uriVars);
}
public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(optionRequestBuilder(accept, urlTemplate, uriVars));
}
public ResultActions option(String urlTemplate, Object... uriVars) throws Exception {
return mockMvc.perform(optionRequestBuilder(urlTemplate, uriVars));
}
/**
* Adds serialized payload to request content
*
* @param request
* @param payload
* @param mediaType
* @param <T>
* @return the request with provided payload as content
* @throws Exception if things go wrong (no registered serializer for payload type and asked MediaType, serialization failure, ...)
*/
public <T> MockHttpServletRequestBuilder feed(
MockHttpServletRequestBuilder request,
final T payload,
final MediaType mediaType) throws Exception {
if (payload == null) {
return request;
}
final SerializationHelper.ByteArrayHttpOutputMessage msg = conv.outputMessage(payload, mediaType);
return request
.headers(msg.headers)
.content(msg.out.toByteArray());
}
}
/**
* Serialize objects to given media type using registered message converters
*/
public class SerializationHelper {
private final ObjectFactory<HttpMessageConverters> messageConverters;
public SerializationHelper(ObjectFactory<HttpMessageConverters> messageConverters) {
this.messageConverters = messageConverters;
}
public <T> ByteArrayHttpOutputMessage outputMessage(final T payload, final MediaType mediaType) throws Exception {
if (payload == null) {
return null;
}
List<HttpMessageConverter<?>> relevantConverters = messageConverters.getObject().getConverters().stream()
.filter(converter -> converter.canWrite(payload.getClass(), mediaType))
.collect(Collectors.toList());
final ByteArrayHttpOutputMessage converted = new ByteArrayHttpOutputMessage();
boolean isConverted = false;
for (HttpMessageConverter<?> converter : relevantConverters) {
try {
((HttpMessageConverter<T>) converter).write(payload, mediaType, converted);
isConverted = true; //won't be reached if a conversion error occurs
break; //stop iterating over converters after first successful conversion
} catch (IOException e) {
//swallow exception so that next converter is tried
}
}
if (!isConverted) {
throw new Exception("Could not convert " + payload.getClass() + " to " + mediaType.toString());
}
return converted;
}
/**
* Provides a String representation of provided payload
*
* @param payload
* @param mediaType
* @param <T>
* @return
* @throws Exception if things go wrong (no registered serializer for payload type and asked MediaType, serialization failure, ...)
*/
public <T> String asString(T payload, MediaType mediaType) throws Exception {
return payload == null ?
null :
outputMessage(payload, mediaType).out.toString();
}
public <T> String asJsonString(T payload) throws Exception {
return asString(payload, MediaType.APPLICATION_JSON_UTF8);
}
public static final class ByteArrayHttpOutputMessage implements HttpOutputMessage {
public final ByteArrayOutputStream out = new ByteArrayOutputStream();
public final HttpHeaders headers = new HttpHeaders();
@Override
public OutputStream getBody() {
return out;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
我试图在一个我的组件中使用Tesseract来执行文件上的ocr。 .ts: .html 我遵循了这个,但是这个错误显示了 我应该怎么做才能让这个工作成功?
首先,我想道歉,如果这个问题是抽象的或不适合本站。我真的不知道还能去哪里问。 目前我已经在iOS和Android上开发了应用程序。它们在firebase中保留所有的状态,因此所有的内容都可以即时保存到firebase实时数据库中。 在创建用户之前,我手动填充实时数据库中的一些数据,这些数据应该存在以便应用程序能够运行,例如用户的配置数据。当我“完成”使用该应用程序时,我直接在Firebase控制台
因此,如果我不能重写字符串作为它的最终结果(因此阻止我重写它的compareTo()方法来调用compareToIgnoreCase()),那么还有其他方法可以实现吗? 任何帮助都是非常感谢的。
我已经成功地在我的Windows机器上安装了gnuradio,并尝试将随附的python环境(Python 2.7)与PyCharm v2018集成。我创建了一个新项目,并为包添加了一个用户定义的路径,以指向所有gnuradio库的位置(C:\Program Files\GNURadio-3.7\lib\site-包)。 在Pycharm可以毫无怨言地看到所有gnuradio包的意义上,一切似乎都
我正在尝试获得一个LocalStack/LocalStack的本地docker实例来使用Node.js aws-sdk库。但我可以得到一个简单的createTopic工作在一个sns客户端。 我已经用这个命令启动了docker映像 docker Run-d-p 4567-4583:4567-4583 LocalStack/LocalStack 我正在运行的代码.... 返回的错误.... [AWS
我已经成功地建立了TailwindCSS上Gridsome以下说明:https://gridsome.org/docs/assets-css/#tailwind 但是,这些说明并没有提到如何设置autoprefixer。因此,我自己尝试了一下,如下所示: npm安装自动刷新器 修改了文件(请参见下面的修改代码,其中包含我所更改内容的注释) 运行 将类添加到以查看是否添加了任何供应商前缀 结果。。。