Spring Security Sping Boot :使用MockMVC进行的单元测试在我添加一个Authorization头时抛出404

4dbbbstv  于 2023-08-05  发布在  Spring
关注(0)|答案(1)|浏览(195)

我有一个可能很愚蠢的问题/问题。首先,我对Spring中的单元测试相当陌生
我有一个API服务,使一个“预订”/“预订”的第三方API,我试图集成。我的API受Spring安全性保护,只要确保传递了有效的JWT,就可以到达我的端点。
我试图创建一个测试,当用户传递一个无效的令牌时,响应正确地是401。现在我从以下几点开始:

mockMvc.perform(post("/booking")
        .contentType(MediaType.APPLICATION_JSON)
        .header("Authorization", "eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9...")
        .content(objectToJson(request)))
    .andExpect(status().isUnauthorized());

字符串
为此,我确实得到了401,但我意识到我忘记了将承载添加到授权值。一旦我添加了承载,它现在抛出404。

mockMvc.perform
                        (post("/booking")
                                .contentType(MediaType.APPLICATION_JSON)
                                .header("Authorization", "Bearer eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9.....")
                                .content(objectToJson(request))
                        )
                .andExpect(status().isUnauthorized());
    }


有人能指出我做错了什么吗?
下面是完整的代码:

控制器

@Operation(summary = "Booking Creation")
    @PostMapping("")
    public Mono<BookingResponse> userBooking(@RequestBody @Valid BookingCreateRequestDTO bookingCreateRequestDTO,
                                             @Parameter(hidden = true) Authentication authentication) throws JsonProcessingException {
        return bookingService.processBookingRequest(bookingCreateRequestDTO);
    }


下面是我的服务层:

public Mono<BookingResponse> processBookingRequest(BookingCreateRequestDTO bookingCreateRequestDTO) throws JsonProcessingException {
        logger.info("Request Processing of Booking Request from client {}", bookingCreateRequestDTO.clientId);
        WebClient webClient = WebClient.builder()
                .baseUrl(xxxx)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .filter(ExchangeFilterFunctions.basicAuthentication(userName, userPwd))
                .build();
        //casting our bookingCreateRequestDTO object to a JSON payload
        String jsonBody = new ObjectMapper().writeValueAsString(bookingCreateRequestDTO);

        return webClient.post()
                .uri(URL)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(jsonBody))
                .retrieve()
                .bodyToMono(BookingResponse.class)
                .timeout(Duration.ofMillis(5000))
                .doOnSuccess(response -> {
                    //Build email template objects
                });
    }


最后是单元测试

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringJUnitConfig
@ContextConfiguration
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@SpringBootTest(classes = {SecurityConfig.class, BookingControllerTest.TestConfig.class})
class BookingControllerTest {

    @Configuration
    public static class TestConfig implements WebMvcConfigurer {

        @Bean
        public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
            return new HandlerMappingIntrospector();
        }

        // Other MVC configuration methods...
    }

    @Mock
    private BookingService bookingService;

    @InjectMocks
    private BookingController bookingController;

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @BeforeEach
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(context)
                .setControllerAdvice(new GeneralBookingException()) // Add error handling advice
                .setControllerAdvice(new UnauthorizedException()) // Add error handling advice
                .apply(springSecurity(springSecurityFilterChain))
                .build();
    }

    @Test
    public void testUserBooking_Unauthorized() throws Exception {
        // Prepare test data
        BookingCreateRequestDTO request = new BookingCreateRequestDTO();
        request.setClientId("client1");

        // Configure the bookingService to throw an exception (Unauthorized)
        given(bookingService.processBookingRequest(request))
                .willThrow(new UnauthorizedException("Unauthorized"));

        // Perform the API call
        mockMvc.perform
                        (post("/booking")
                                .contentType(MediaType.APPLICATION_JSON)
                                .header("Authorization", "Bearer eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9.....")
                                .content(objectToJson(request))
                        )
                .andExpect(status().isUnauthorized());
    }

    // Helper method to convert object to JSON string
    private String objectToJson(Object object) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(object);
    }
}


附加说明这里是我的构建文件

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
    id("org.springdoc.openapi-gradle-plugin") version "1.6.0"
    id 'com.google.cloud.tools.jib' version '3.3.1'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation group: 'org.springframework.security', name: 'spring-security-oauth2-client', version: '6.1.0'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    implementation group: 'org.hibernate', name: 'hibernate-validator', version: '8.0.0.Final'
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.1.0'
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-api', version: '2.1.0'
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webflux-ui', version: '2.1.0'
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webflux-api', version: '2.1.0'
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-common', version: '2.1.0'

    implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.2'
    implementation group: 'com.sendgrid', name: 'sendgrid-java', version: '4.9.3'
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
    implementation group: 'com.google.guava', name: 'guava', version: '32.1.1-jre'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation group: 'org.springframework', name: 'spring-test', version: '6.0.11'
    testImplementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation group: 'org.springframework.security', name: 'spring-security-test', version: '6.1.0'

    testImplementation 'io.projectreactor:reactor-test'
}

tasks.named('test', Test) {
    useJUnitPlatform()
    testLogging {
        exceptionFormat "full"
        events "started", "skipped", "passed", "failed"
        showStandardStreams true
    }


最后是我的Spring安全配置文件:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    String jwkSetUri;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/swagger-ui/index.html",
                                "/swagger-resources/**",
                                "/api-docs/swagger-config",
                                "/api-docs/*",
                                "/swagger-ui/*",
                                "/my/docs",
                                "/error",
                                "/favicon.ico",
                                "/*/*.png",
                                "/*/*.gif",
                                "/*/*.svg",
                                "/*/*.jpg",
                                "/*/*.html",
                                "/*/*.css").anonymous()
                                .anyRequest().authenticated()
                )
                .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));

        // @formatter:on
        return http.build();
    }

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
    }

unhi4e5o

unhi4e5o1#

只要使用MockMvc,就可以使用SecurityMockMvcRequestPostProcessors.jwt()中的I contributed to spring-security-test
要使用我为OAuth2编写的注解,您需要spring-addons-oauth2-test。关于我的Github repo
我写了一篇article on Baeldung,涵盖了使用模拟的OAuth2身份测试Spring组件和应用程序的主题,但是注解最近有了很大的改进,文章还没有更新(Baeldung的编辑过程可能需要一些时间)。也就是说,本文中涉及SecurityMockMvcRequestPostProcessors的部分仍然适用。
几乎所有我写的回答这个other question是适用于你。只有一个细节:@ScorprocS正在使用“我的”Sping Boot starter(spring-addons-starter-oidc)以及spring-boot-starter-oauth2-resource-server,在我的回答中使用spring-addons-starter-oidc-test的原因。如果您没有使用spring-addons-starter-oidcspring-addons-oauth2-test(只包含测试注解)就足够了。

相关问题