簡體   English   中英

在Spring Boot中測試Spring Security篩選器需要哪些組件?

[英]What pieces are needed to test a Spring Security Filter in Spring Boot?

因此,我正在嘗試測試我的安全篩選器,感覺好像缺少明顯的東西。 該代碼的某些部分過去曾起作用過,我將我的項目分成幾個庫(並從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)

我需要做些什么才能讓我的測試上下文調用過濾器?

我缺少適當的WebSecurityAdapter配置

@TestConfiguration
static class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure( HttpSecurity http ) throws Exception {
        http.authorizeRequests()
            .mvcMatchers( JsonAuthenticationFilter.AUTH_PASS ).permitAll()
            .anyRequest().authenticated()
            .and().csrf().disable();
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM