Careeroza
AuthenticationEntryPoint
A JSON 401
By default a stateless API returns an empty 401 (or redirects to a login page if form login is on). A REST client wants JSON. First a small error record shared by all handlers:
public record ApiError(int status, String error, String message, String path) {}package in.shopease.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper mapper;
public JsonAuthenticationEntryPoint(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException ex) throws IOException {
String tokenError = (String) request.getAttribute(JwtAuthFilter.AUTH_ERROR);
String message;
if (tokenError != null) {
message = tokenError; // "Access token expired"
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer error=\"invalid_token\"");
} else if (ex instanceof BadCredentialsException) {
message = "Invalid email or password";
} else {
message = "Authentication required";
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer");
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getOutputStream(),
new ApiError(401, "Unauthorized", message, request.getRequestURI()));
}
}Responses
Example
No token, expired token:
curl -i localhost:8080/api/orders
curl -i localhost:8080/api/orders -H "Authorization: Bearer $EXPIRED_TOKEN"Example output
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{"status":401,"error":"Unauthorized","message":"Authentication required","path":"/api/orders"}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
Content-Type: application/json
{"status":401,"error":"Unauthorized","message":"Access token expired","path":"/api/orders"}