【发布时间】:2018-02-06 06:21:16
【问题描述】:
所以我正在尝试测试我的安全过滤器,但我觉得我遗漏了一些明显的东西。这段代码的某些部分在过去有效,我将我的项目分成几个库(并从 maven 迁移到 gradle),所以很可能我已经离开了类路径中的依赖项或某些库。让我们从测试开始
@SpringBootTest
@AutoConfigureMockMvc
@RunWith( SpringRunner.class )
public class AuthenticationTest extends AbstractSecurityTest {
@Autowired
private MockMvc mvc;
@Autowired
private Gson gson;
@SpyBean
private JsonAuthenticationFilter filter;
@Test
public void validLogin() throws Exception {
log.debug( "posting password" );
String json = gson.toJson( new PasswordCredential( name, pass ) );
this.mvc.perform(
post( JsonAuthenticationFilter.AUTH_PASS )
.contentType( MediaType.APPLICATION_JSON )
.content( json ) )
.andExpect( status().is2xxSuccessful() )
.andExpect( cookie().exists( "SESSION" ) );
verify( filter, times( 1 ) )
.doFilter( any( ServletRequest.class ), any( ServletResponse.class ), any( FilterChain.class ) );
}
@Test
public void invalidLogin() throws Exception {
String json = gson.toJson( new PasswordCredential( "xenoterracide@gmail.com", "password" ) );
this.mvc.perform(
post( JsonAuthenticationFilter.AUTH_PASS )
.contentType( MediaType.APPLICATION_JSON )
.accept( MediaType.APPLICATION_JSON )
.content( json ) )
.andExpect( status().is4xxClientError() )
.andExpect( status().isUnauthorized() );
verify( filter, times( 1 ) )
.doFilter( any( ServletRequest.class ), any( ServletResponse.class ), any( FilterChain.class ) );
}
@Test
public void unsupportedMediaType() throws Exception {
this.mvc.perform(
post( JsonAuthenticationFilter.AUTH_PASS )
.param( "pass", "password" )
.param( "user", "xenoterracide@gmail.com" ) )
.andExpect( status().is4xxClientError() );
verify( filter, times( 1 ) )
.doFilter( any( ServletRequest.class ), any( ServletResponse.class ), any( FilterChain.class ) );
}
}
我的假设是 @AutoConfigureMockMvc 默认情况下应该包含过滤器。
这是我的身份验证过滤器
@Component
class JsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
static final String AUTH_PASS = Routes.PUBLIC + "/authentication/password";
private static final RequestMatcher MATCHER = new AndRequestMatcher( Arrays.asList(
new AntPathRequestMatcher( AUTH_PASS, HttpMethod.POST.name() ),
new MediaTypeRequestMatcher( new ContentTypeContentNegotiationStrategy(),
MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8 )
) );
private final ObjectMapper objectMapper;
private final Validator validator;
protected JsonAuthenticationFilter( ObjectMapper objectMapper, Validator validator ) {
super( MATCHER );
this.objectMapper = objectMapper;
this.validator = validator;
}
@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
throws AuthenticationException, IOException {
PasswordCredential credentials = objectMapper.readValue( request.getReader(), PasswordCredential.class );
try {
DataBinder dataBinder = new DataBinder( credentials );
dataBinder.setBindingErrorProcessor( new DefaultBindingErrorProcessor() );
dataBinder.setValidator( validator );
dataBinder.validate();
dataBinder.close();
}
catch ( BindException e ) {
throw new BadRequestException( "field errors", e );
}
AbstractAuthenticationToken authRequest = credentials.toAuthenticationToken();
setDetails( request, authRequest );
return this.getAuthenticationManager().authenticate( authRequest );
}
private void setDetails( HttpServletRequest request, AbstractAuthenticationToken authRequest ) {
authRequest.setDetails( authenticationDetailsSource.buildDetails( request ) );
}
@Override
@Autowired( required = false )
public void setRememberMeServices( RememberMeServices rememberMeServices ) {
super.setRememberMeServices( rememberMeServices );
}
@Override
@Autowired
public void setAuthenticationManager( AuthenticationManager authenticationManager ) {
super.setAuthenticationManager( authenticationManager );
}
}
我知道它是@Autowired,并设置了AuthenticationManager,但没有调用继承的doFilter。
plugins {
`java-library`
`maven-publish`
`checkstyle`
`idea`
id("com.github.spotbugs").version("1.6.0")
id("net.ltgt.errorprone").version("0.0.13")
id("io.spring.dependency-management").version("1.0.4.RELEASE")
}
repositories {
maven(System.getenv("JAR_REPOSITORY_URI"))
jcenter()
}
group = "com.xenoterracide.rpf"
version = "0.1.0-SNAPSHOT"
description = "Security DTOs"
configurations.all({
resolutionStrategy({
cacheChangingModulesFor(1, TimeUnit.MINUTES)
})
})
dependencyManagement {
resolutionStrategy({
cacheChangingModulesFor(1, TimeUnit.MINUTES)
})
imports {
mavenBom("com.xenoterracide:bom:0.1.0-SNAPSHOT")
}
}
// In this section you declare the dependencies for your production and test code
dependencies {
errorprone("com.google.guava:guava:latest.release")
errorprone("com.google.errorprone:error_prone_core:latest.release")
compileOnly("com.google.code.findbugs:jsr305")
compileOnly("com.google.errorprone:error_prone_annotations:latest.release")
implementation("javax.validation:validation-api")
implementation("javax.servlet:javax.servlet-api")
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("org.hibernate:hibernate-validator")
implementation("org.springframework:spring-core")
implementation("org.springframework:spring-context")
implementation("org.springframework:spring-tx")
implementation("org.springframework.data:spring-data-commons")
implementation("org.springframework.data:spring-data-jpa")
implementation("org.springframework.security:spring-security-core")
implementation("org.springframework.security:spring-security-web")
implementation("com.xenoterracide.rpf:constants:0.1.0-SNAPSHOT")
implementation("com.xenoterracide.rpf:sec-dtos:0.1.0-SNAPSHOT")
implementation("com.xenoterracide.rpf:sec:0.1.0-SNAPSHOT")
testRuntimeOnly("com.h2database:h2")
testRuntimeOnly("org.springframework.boot:spring-boot-starter-security")
testRuntimeOnly("org.springframework.boot:spring-boot-starter-data-jpa")
testImplementation("junit:junit")
testImplementation("org.mockito:mockito-core")
testImplementation("org.apache.commons:commons-lang3")
testImplementation("com.google.code.gson:gson")
testImplementation("org.assertj:assertj-core")
testImplementation("org.springframework:spring-test")
testImplementation("org.springframework.boot:spring-boot-test")
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")
}
更新添加请求的类
class ContentTypeContentNegotiationStrategy implements ContentNegotiationStrategy {
/**
* {@inheritDoc}
*
* @throws HttpMediaTypeNotAcceptableException if the 'Content-Type' header cannot be parsed
*/
@Override
public List<MediaType> resolveMediaTypes( NativeWebRequest request )
throws HttpMediaTypeNotAcceptableException {
String[] headerValueArray = request.getHeaderValues( HttpHeaders.CONTENT_TYPE );
if ( headerValueArray == null ) {
return Collections.emptyList();
}
List<String> headerValues = Arrays.asList( headerValueArray );
try {
List<MediaType> mediaTypes = MediaType.parseMediaTypes( headerValues );
MediaType.sortBySpecificityAndQuality( mediaTypes );
return mediaTypes;
}
catch ( InvalidMediaTypeException ex ) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage() );
}
}
}
以及测试失败的输出
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v0/public/authentication/password
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Full authentication is required to access this resource
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Strict-Transport-Security=[max-age=31536000 ; includeSubDomains], WWW-Authenticate=[Basic realm="Spring"]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v0/public/authentication/password
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json]}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Full authentication is required to access this resource
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Strict-Transport-Security=[max-age=31536000 ; includeSubDomains], WWW-Authenticate=[Basic realm="Spring"]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v0/public/authentication/password
Parameters = {pass=[password], user=[xenoterracide@gmail.com]}
Headers = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Full authentication is required to access this resource
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Strict-Transport-Security=[max-age=31536000 ; includeSubDomains], WWW-Authenticate=[Basic realm="Spring"]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Wanted but not invoked:
jsonAuthenticationFilter.doFilter(
<any>,
<any>,
<any>
);
-> at com.xenoterracide.rpf.sec.authn.AuthenticationTest.unsupportedMediaType(AuthenticationTest.java:76)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
jsonAuthenticationFilter.doFilter(
<any>,
<any>,
<any>
);
-> at com.xenoterracide.rpf.sec.authn.AuthenticationTest.unsupportedMediaType(AuthenticationTest.java:76)
Actually, there were zero interactions with this mock.
at com.xenoterracide.rpf.sec.authn.AuthenticationTest.unsupportedMediaType(AuthenticationTest.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
我需要做什么才能让我的测试上下文调用过滤器?
【问题讨论】:
-
我会假设您的请求匹配器不匹配。我想你可以很容易地调试来检查。您并没有真正说出
ContentTypeContentNegotiationStrategy在做什么。也许看看那里? -
@DaveSyer line 199 of
AbstractAuthenticationProcessingFilter.doFilter调用if (!requiresAuthentication(request, response)) {在我的代码中实际上调用了MATCHER,但从未调用过doFilter,因此它永远不会命中该代码。虽然添加了那个类,以防万一 -
那看起来很犹太。您可能必须创建一个独立的示例才能深入了解这一点。基本上,它适用于我,到目前为止我们所知道的作品。
标签: java spring spring-boot spring-security servlet-filters