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"); } }
Advertisement
Answer
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: