Spring Security custom JSON authentication failure response

This article details how to customize the authentication entry point (AuthenticationEntryPoint) in Spring Security to return a formatted JSON error response instead of the default HTML page when the user accesses protected resources without authentication. By configuring `CustomAuthenticationEntryPoint` and writing JSON data directly to `HttpServletResponse`, developers can provide a more friendly and consistent error handling mechanism for API clients.
When building a RESTful API, a unified error response format is crucial. When Spring Security handles unauthenticated requests, it will return an error page in HTML format (such as HTTP Status 401 Unauthorized) by default. This may work for browser clients, but for API clients that require responses in JSON format, this default behavior is not ideal. This tutorial will guide you on how to solve this problem by customizing the AuthenticationEntryPoint to return a structured JSON error message.
Problem with default authentication failure response
When Spring Security detects an unauthenticated request trying to access a protected resource, it triggers an AuthenticationEntryPoint. By default, this usually results in the browser redirecting to the login page or returning a 401 Unauthorized response containing HTML content. For API consumers, the expected response is usually in JSON format like this:
{
"errors": [
{
"status": "401",
"title": "UNAUTHORIZED",
"detail": "Authentication failed or missing authentication credentials"
}
]
}
What you actually receive may be:
<title>HTTP Status 401 – Unauthorized</title>
<!-- ... style... -->
<h1>HTTP Status 401 – Unauthorized</h1>
Obviously, this HTML response is not suitable for automated parsing by API clients.
Custom AuthenticationEntryPoint
To implement an authentication failure response in JSON format, we need to create a custom AuthenticationEntryPoint implementation. This class will be responsible for writing the JSON data we expect directly to HttpServletResponse when authentication fails.
First, define your custom AuthenticationEntryPoint:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
//Set the response content type to JSON
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//Set the HTTP status code to 401 Unauthorized
response.setStatus(HttpStatus.UNAUTHORIZED.value());
// Optional: Add WWW-Authenticate header, necessary for Basic authentication response.addHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
// Construct JSON error body Map<string object> errorDetails = Map.of(
"status", String.valueOf(HttpStatus.UNAUTHORIZED.value()),
"title", HttpStatus.UNAUTHORIZED.name(),
"detail", authException.getMessage() != null ? authException.getMessage() : "Authentication failed or authentication credentials are missing"
);
Map<string object> errorResponse = Collections.singletonMap("errors", Collections.singletonList(errorDetails));
//Write JSON into the response body try (PrintWriter writer = response.getWriter()) {
objectMapper.writeValue(writer, errorResponse);
}
}
}</string></string>
Code analysis:
- @Component : Register CustomAuthenticationEntryPoint as a Spring Bean for injection in Spring Security configuration.
- Commence method : This is the core method of the AuthenticationEntryPoint interface and is called when an unauthenticated user attempts to access a protected resource.
- response.setContentType(MediaType.APPLICATION_JSON_VALUE) : The key step is to set the Content-Type of the response to application/json to inform the client that JSON data is returned.
- response.setStatus(HttpStatus.UNAUTHORIZED.value()) : Set the HTTP status code to 401, indicating not authenticated.
- response.addHeader("WWW-Authenticate", "Basic realm=\"Realm\"") : If you are using HTTP Basic authentication, this header is required and will prompt the client to provide authentication information.
- Build JSON body : ObjectMapper is used here to serialize Java Map objects into JSON strings. This method is more robust and recommended than manually concatenating strings. You can customize the JSON structure and error information according to actual needs.
- response.getWriter() : Get the PrintWriter object, through which the generated JSON string is written into the response body.
Configure Spring Security to use a custom EntryPoint
Next, you need to register and use this custom AuthenticationEntryPoint in Spring Security's configuration class.
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
// Inject custom AuthenticationEntryPoint through the constructor
public SecurityConfiguration(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable() // Disable CSRF protection, usually API does not require .authorizeRequests()
.antMatchers(HttpMethod.GET, "/public/**").permitAll() // Allow GET requests to access the /public/** path.anyRequest().authenticated() // All other requests require authentication.and()
.httpBasic() // Enable HTTP Basic authentication.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint); // Specify a custom authentication entry point}
}
Configuration analysis:
- @Configuration and @EnableWebSecurity : mark this as a Spring Security configuration class.
- Constructor injection : Inject CustomAuthenticationEntryPoint into SecurityConfiguration.
- httpSecurity.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint) : This is the core configuration, telling Spring Security to use our custom customAuthenticationEntryPoint to handle when authentication fails.
- httpBasic() : Enable HTTP Basic authentication. If your API uses other authentication methods (such as JWT), this part of the configuration will be different.
- authorizeRequests() : Defines which requests require authentication and which can be publicly accessed.
Test custom responses
In order to verify that our custom AuthenticationEntryPoint works as expected, we can write an integration test. Spring Boot Test and MockMvc are used here to simulate HTTP requests.
package com.example.security.custom.entrypoint;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest // Only load beans related to the Web layer
@Import({SecurityConfiguration.class, CustomAuthenticationEntryPoint.class}) // Import Security configuration and EntryPoint
class SecurityCustomEntrypointApplicationTests {
@Autowired
private MockMvc mvc;
@Test
void testUnauthorizedAccessReturnsJson() throws Exception {
mvc
.perform(post("/somewhere")) // Simulate an unauthenticated POST request to a protected path. andDo(print()) // Print request and response details for easy debugging. andExpectAll(
status().isUnauthorized(), // Expect HTTP status code to be 401
header().exists("WWW-Authenticate"), // Expect WWW-Authenticate in the response header
jsonPath("$.errors[0].detail").exists(), // Expect the JSON path errors[0].detail to exist jsonPath("$.errors[0].title").value("UNAUTHORIZED"), // Expect the value of the JSON path errors[0].title to be "UNAUTHORIZED"
jsonPath("$.errors[0].status").value(401) // Expect the value of JSON path errors[0].status to be 401
);
}
}
Test analysis:
- @WebMvcTest : Focus on testing Spring MVC components and will not start the complete Spring Boot application.
- @Import : Explicitly import SecurityConfiguration and CustomAuthenticationEntryPoint to ensure that the Spring Security configuration takes effect in the test environment.
- MockMvc : used to simulate HTTP requests and verify responses.
- perform(post("/somewhere")) : Sends a POST request to /somewhere, assuming this is a protected path and no authentication credentials are provided.
- andExpectAll(...) : Use multiple assertions to validate the response:
- status().isUnauthorized(): Check whether the HTTP status code is 401.
- header().exists("WWW-Authenticate"): Check whether the WWW-Authenticate header exists.
- jsonPath(...): Use JSONPath expressions to verify the content and structure of the JSON response body.
Things to note and best practices
- Use ObjectMapper : In actual projects, it is strongly recommended to use Jackson's ObjectMapper (or other JSON libraries such as Gson) to serialize Java objects to JSON strings instead of manually splicing strings. This avoids formatting errors and allows for better handling of complex objects.
- Internationalization of error information : In actual applications, error information (such as the detail field) may need to support internationalization. You can inject a MessageSource into CustomAuthenticationEntryPoint to obtain localized error information.
- AccessDeniedHandler : AuthenticationEntryPoint only handles access from unauthenticated users. If the user is authenticated but does not have permission to access a resource (i.e. authorization fails), you need to implement AccessDeniedHandler to provide a similar JSON error response.
- Logging : Add appropriate logging in the commence method to track authentication failure events in production.
- Custom error codes : In addition to HTTP status codes, you can define your own business error codes in the JSON response to provide more fine-grained error classification.
Summarize
By customizing Spring Security's AuthenticationEntryPoint, you can easily replace the default HTML authentication failure response with a structured JSON response. This is critical for building modern RESTful APIs, ensuring that API clients receive consistent and easy-to-parse error messages, thereby improving user experience and system maintainability. Combined with ObjectMapper and rigorous testing, you can build a robust and professional API error handling mechanism.
The above is the detailed content of Spring Security custom JSON authentication failure response. For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
AI Clothes Remover
Online AI tool for removing clothes from photos.
Undresser.AI Undress
AI-powered app for creating realistic nude photos
ArtGPT
AI image generator for creative art from text prompts.
Stock Market GPT
AI powered investment research for smarter decisions
Hot Article
Popular tool
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
Hot Topics
20521
7
13633
4
How to configure Spark distributed computing environment in Java_Java big data processing
Mar 09, 2026 pm 08:45 PM
Spark cannot run in local mode, ClassNotFoundException: org.apache.spark.sql.SparkSession. This is the most common first step of getting stuck: even the dependencies are not correct. Only spark-core_2.12 is written in Maven, but spark-sql_2.12 is not added. SparkSession crashes as soon as it is built. The Scala version must strictly match the official Spark compiled version - Spark3.4.x uses Scala2.12 by default. If you use spark-sqljar of 2.13, the class loader cannot directly find the main class. Practical advice: Go to mvnre
How to safely map user-entered weekday string to integer value and implement date offset operation in Java
Mar 09, 2026 pm 09:43 PM
This article introduces a concise and maintainable way to map the weekday string (such as "Monday") to the corresponding serial number (1-7), and use the modulo operation to realize the forward and backward offset of any number of days (such as Monday plus 4 days to get Friday), avoiding lengthy if chains and hard-coded logic.
How to use Homebrew to install Java on Mac_A must-have Java tool chain for developers
Mar 09, 2026 pm 09:48 PM
Homebrew installs the latest stable version of openjdk (such as JDK22) by default, not the LTS version; you need to explicitly execute brewinstallopenjdk@17 or brewinstallopenjdk@21 to install the LTS version, and manually configure PATH and JAVA_HOME to be correctly recognized by the system and IDE.
What is exception masking (Suppressed Exceptions) in Java_Multiple resource shutdown exception handling
Mar 10, 2026 pm 06:57 PM
What is SuppressedException: It is not "swallowed", but actively archived by the JVM. SuppressedException is not an exception loss, but the JVM quietly attaches the secondary exception to the main exception under the premise that "only one exception must be thrown" for you to verify afterwards. It is automatically triggered by the JVM in only two scenarios: one is that the resource closure in try-with-resources fails, and the other is that you manually call addSuppressed() in finally. The key difference is: the former is fully automatic and safe; the latter requires you to keep it to yourself, and it can be written as shadowing if you are not careful. try-
How to correctly implement runtime file writing in Java applications (avoiding JAR internal write failures)
Mar 09, 2026 pm 07:57 PM
After a Java application is packaged as a JAR, data cannot be written directly to the resources in the JAR package (such as test.txt) because the JAR is essentially a read-only ZIP archive; the correct approach is to write variable data to an external path (such as a user directory, a temporary directory, or a configuration-specified path).
What is the underlying principle of array expansion in Java_Java memory dynamic adjustment analysis
Mar 09, 2026 pm 09:45 PM
ArrayList.add() triggers expansion because grow() is called when size is equal to elementData.length. The first add allocates 10 capacity, and subsequent expansion is 1.5 times and not less than the minimum requirement, relying on delayed initialization and System.arraycopy optimization.
Complete tutorial on reading data from file and initializing two-dimensional array in Java
Mar 09, 2026 pm 09:18 PM
This article explains in detail how to load an integer sequence in an external text file into a Java two-dimensional array according to a specified row and column structure (such as 2500×100), avoiding manual assignment or index out-of-bounds, and ensuring accurate data order and robust and reusable code.
A concise method in Java to compare whether four byte values are equal and non-zero
Mar 09, 2026 pm 09:40 PM
This article introduces several professional solutions for efficiently and safely comparing multiple byte type return values (such as getPlayer()) in Java to see if they are all equal and non-zero. We recommend two methods, StreamAPI and logical expansion, to avoid Boolean and byte mis-comparison errors.





