-
Notifications
You must be signed in to change notification settings - Fork 41.6k
Closed
Labels
status: supersededAn issue that has been superseded by anotherAn issue that has been superseded by another
Description
In general, for any exception, the default behavior if you tell Spring Boot to include the error message via server.error.include-message=always
is to include the Exception
message in the JSON message
attribute.
Looks like with any exception whose ancestor is org.springframework.security.core.AuthenticationException
(like AccessDeniedException
, AccountExpiredException
, BadCredentialsException
,...), the behavior differs and the message
value contains the same value as error
attribute.
Sample pom file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.eu.rubensa.springboot.error</groupId>
<artifactId>springboot-error-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-error-test</name>
<description>Project for testing Spring Boot error handling</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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>
</dependencies>
</project>
Sample test class:
package org.eu.rubensa.springboot.error;
import com.fasterxml.jackson.databind.JsonNode;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* The @SpringBootTest annotation will load the fully ApplicationContext. This
* will not use slicing and scan for all the stereotype annotations
* (@Component, @Service, @Respository and @Controller / @RestController) and
* loads the full application context.
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
// From Spring 2.3.0 "server.error.include-message" and
// "server.error.include-binding-errors" is set to "never"
properties = { "server.error.include-message=always",
/**
* When you add the Security starter without custom security configurations,
* Spring Boot endpoints will be secured using HTTP basic authentication with a
* default user and generated password. To override that, you can configure
* credentials in application.properties as follows
*/
"spring.security.user.name=username", "spring.security.user.password=password" })
public class AuthenticationExceptionMessageInconsistencyTest {
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void testExceptionMessage() throws Exception {
String exceptionParam = "custom";
final ResponseEntity<JsonNode> response = testRestTemplate.withBasicAuth("username", "password")
.exchange("/exception/{exception_id}", HttpMethod.GET, null, JsonNode.class, exceptionParam);
Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
JsonNode jsonResponse = response.getBody();
Assertions.assertThat(jsonResponse.findValue("status").asInt()).isEqualTo(500);
Assertions.assertThat(jsonResponse.findValue("error").asText()).isEqualTo("Internal Server Error");
// This is the exception message
Assertions.assertThat(jsonResponse.findValue("message").asText()).isEqualTo("Custom exception");
Assertions.assertThat(jsonResponse.findValue("path").asText()).isEqualTo("/exception/custom");
}
@Test
public void testAuthenticationExceptionMessage() throws Exception {
String exceptionParam = "custom-authentication";
final ResponseEntity<JsonNode> response = testRestTemplate.withBasicAuth("username", "password")
.exchange("/exception/{exception_id}", HttpMethod.GET, null, JsonNode.class, exceptionParam);
Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
JsonNode jsonResponse = response.getBody();
Assertions.assertThat(jsonResponse.findValue("status").asInt()).isEqualTo(401);
Assertions.assertThat(jsonResponse.findValue("error").asText()).isEqualTo("Unauthorized");
// This should be the exception message but is the same as error
Assertions.assertThat(jsonResponse.findValue("message").asText()).isEqualTo("Unauthorized");
Assertions.assertThat(jsonResponse.findValue("path").asText()).isEqualTo("/exception/custom-authentication");
}
/**
* A nested @Configuration class wild be used instead of the application’s
* primary configuration.
* <p>
* Unlike a nested @Configuration class, which would be used instead of your
* application’s primary configuration, a nested @TestConfiguration class is
* used in addition to your application’s primary configuration.
*/
@Configuration
/**
* Tells Spring Boot to start adding beans based on classpath settings, other
* beans, and various property settings.
*/
@EnableAutoConfiguration
/**
* The @ComponentScan tells Spring to look for other components, configurations,
* and services in the the TestWebConfig package, letting it find the
* TestController class.
* <p>
* We only want to test the classes defined inside this test configuration
*/
static class TestConfig {
@RestController
public class TestController {
@GetMapping("/exception/{exception_id}")
public void getSpecificException(@PathVariable("exception_id") String pException) {
if ("custom".equals(pException)) {
throw new CustomException("Custom exception");
} else if ("custom-authentication".equals(pException)) {
throw new MyAuthenticationException("Custom authentication exception");
}
}
}
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
public class MyAuthenticationException extends AuthenticationException {
public MyAuthenticationException(String message) {
super(message);
}
}
}
}
Spring Boot Version: 2.4.5
Metadata
Metadata
Assignees
Labels
status: supersededAn issue that has been superseded by anotherAn issue that has been superseded by another