您可以定义一个 AuthenticationEntryPoint 并使用给定的 HttpServletResponse 根据需要编写您的响应主体。
像这样扩展(例如)BasicAuthenticationEntryPoint(没有多少配置发送这个“WWW-Authenticated”标头):
@Bean
public AuthenticationEntryPoint accessDeniedHandler() {
BasicAuthenticationEntryPoint result = new BasicAuthenticationEntryPoint() {
// inline:
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.addHeader( // identic/similar to super method
"WWW-Authenticate", String.format("Basic realm="%s"", getRealmName())
);
// subtle difference:
response.setStatus(HttpStatus.UNAUTHORIZED.value() /*, no message! */);
// "print" custom to "response":
response.getWriter().format(
"{"error":{"message":"%s"}}", authException.getMessage()
);
}
};
// basic specific/default:
result.setRealmName("Realm");
return result;
}
这些测试通过:
package com.example.security.custom.entrypoint;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.empty;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
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.*;
@AutoConfigureMockMvc
@SpringBootTest(properties = {"spring.security.user.password=!test2me"})
class SecurityCustomEntrypointApplicationTests {
@Autowired
private MockMvc mvc;
@Test
public void testUnathorized() throws Exception {
mvc
.perform(get("/secured").with(httpBasic("unknown", "wrong")))
.andDo(print())
.andExpect(unauthenticated());
}
@Test
void testOk() throws Exception {
mvc
.perform(get("/secured").with(httpBasic("user", "!test2me")))
.andDo(print())
.andExpectAll(
status().isOk(),
content().string("Hello")
);
}
@Test
void testAccessDenied() throws Exception {
mvc
.perform(get("/secured"))
.andDo(print())
.andExpectAll(
status().isUnauthorized(),
header().exists("WWW-Authenticate"),
jsonPath("$.error.message", not(empty()))
);
}
}
在这个(完整的)应用程序上:
package com.example.security.custom.entrypoint;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import static org.springframework.security.config.Customizer.withDefaults;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@SpringBootApplication
public class SecurityCustomEntrypointApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityCustomEntrypointApplication.class, args);
}
@Controller
static class SecuredController {
@GetMapping("secured")
@ResponseBody
public String secured() {
return "Hello";
}
}
@Configuration
static class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http,
AuthenticationEntryPoint authenticationEntryPoint
) throws Exception {
http
.authorizeHttpRequests(
(requests) -> requests
.antMatchers("/secured").authenticated()
.anyRequest().permitAll()
)
.httpBasic(withDefaults())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint) // ...
;
return http.build();
}
@Bean
public AuthenticationEntryPoint accessDeniedHandler() {
BasicAuthenticationEntryPoint result = new BasicAuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.addHeader(
"WWW-Authenticate", String.format("Basic realm="%s"", getRealmName())
);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().format(
"{"error":{"message":"%s"}}", authException.getMessage()
);
}
};
result.setRealmName("Realm");
return result;
}
}
}