1

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

1 Answer 1

3

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:

Sign up to request clarification or add additional context in comments.

1 Comment

I see no need (->other beans) to @Bean/@Component (spring-manage) the entry point, but we can easily replace private static AuthenticationEntryPoint with @Bean public AuthenticationEntryPoint ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.