How is it possible to return a json instead a html?
I got:
<!doctype html>
<html lang="en">
<head>
<title>HTTP Status 401 – Unauthorized</title>
<style type="text/css">
body {
font-family: Tahoma, Arial, sans-serif;
}
h1,
h2,
h3,
b {
color: white;
background-color: #525D76;
}
h1 {
font-size: 22px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 14px;
}
p {
font-size: 12px;
}
a {
color: black;
}
.line {
height: 1px;
background-color: #525D76;
border: none;
}
</style>
</head>
<body>
<h1>HTTP Status 401 – Unauthorized</h1>
</body>
</html>
i need something like this:
{
"errors": [
{
"status": "401",
"title": "UNAUTHORIZED",
"detail": "xyz ..."
}
]
}
My Adapter:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
// @formatter:off
httpSecurity
.csrf()
.disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
// @formatter:on
}
}
The CustomAuthenticationEntryPoint
@Component
class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint
{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException
{
Collection<String> authorities = response.getHeaders("Authorization");
response.addHeader("access_denied_reason", "authentication_required");
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized");
}
}
Pragmatically we can print/write to response[.getWriter()]
within our entry point, like:
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
//...
private static AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
response.addHeader( // identic/similar to "basic" entry point
"WWW-Authenticate", "Basic realm=\"Realm\""
);
// subtle difference to "basic" entry point :
// better/looks like:
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// response.addHeader...
response.setStatus(HttpStatus.UNAUTHORIZED.value() /*, no message!(?) */);
// "print" custom to "response" (with String#format, jackson, gson... (templating fw...)):
response.getWriter().format("""
{
"errors":[
{
"status": %d,
"title": "%s",
"detail": "%s"
}
]
}
""",
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.name(),
authException.getMessage() // or whatever message/params/format we see fit
);
};
}
BasicAuthenticationEntryPoint@github
Then we can pass a test like:
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.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
class SecurityCustomEntrypointApplicationTests {
@Autowired
private MockMvc mvc;
@Test
void testUnauthorized() throws Exception {
mvc
.perform(post("/somewhere")) // no (basic) credentials(!), assuming no 404 :)
.andDo(print())
.andExpectAll(
status().isUnauthorized(),
header().exists("WWW-Authenticate"),
jsonPath("$.errors[0].detail").exists(),
jsonPath("$.errors[0].title").value("UNAUTHORIZED"),
jsonPath("$.errors[0].status").value(401) // , ...
);
}
}
To make it work for basic authetication and "wrong credentials" see also: https://stackoverflow.com/a/74547059/592355 .
Dup/Related: