Spring Boot can't intercept actuator access - java

In SpringBoot version 2.1.6 unable to intercept access actuator request
Now I have a global interceptor
#Component
public class ServiceFilter implements HandlerInterceptor {
//log4j
static final Logger logger = LogManager.getLogger(ServiceFilter.class);
private final RateLimiter limiter = RateLimiter.create(Runtime.getRuntime().availableProcessors() * 2 + 1);
private final ThreadLocal<ExecuteRecordDto> executeRecord = new ThreadLocal<>();
public ServiceFilter() {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ExecuteRecordDto recordDto = ExecuteRecordDto.bulider(request);
executeRecord.set(recordDto);
if (!limiter.tryAcquire()) {
logger.warn("rate limiter ; json logger : {}",CommonUtil.toJSONString(recordDto));
response.getWriter().print(CommonUtil.toJSONString(ResultStatus.status(407, "rate limiter")));
return false;
}
if (ObjectUtils.isEmpty(request.getHeader("Authorization"))) {
logger.warn("illegal request, json logger : {} ",CommonUtil.toJSONString(recordDto));
response.getWriter().print(CommonUtil.toJSONString(ResultStatus.status(403, "Permission denied")));
return false;
}
switch (TokenHandle.checkToken(request.getHeader("Authorization"))) {
//正常放行token
case 0:
response.getWriter().print(CommonUtil.toJSONString(ResultStatus.status(407, "rate limiter")));
return true;
//token 过期
case 1:
response.getWriter().println(CommonUtil.toJSONString(ResultStatus.status(408, "Token expire")));
break;
//非法token
case 2:
logger.warn("illegal token, json logger : {} ",CommonUtil.toJSONString(recordDto));
response.getWriter().print(CommonUtil.toJSONString(ResultStatus.status(409, "Illegal token ")));
break;
default:
throw new RuntimeException("server runtime exception");
}
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ExecuteRecordDto recordDto = executeRecord.get();
logger.info("json logger : {}",CommonUtil.toJSONString(recordDto));
executeRecord.remove();
}
}
And make it work
#Configuration
public class ConfigFilter implements WebMvcConfigurer {
private final ServiceFilter filter;
#Autowired
public ConfigFilter(ServiceFilter filter){
this.filter = filter;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(filter).addPathPatterns("/**");
}
}
I requested my own api, get the effect I want
How can SpringBoot intercept a visit to actuator

Actuator is using a different HandlerMapping (see: org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping).
This Handlermapping will be chosen over your configured RequestHandlerMapping because of the order (-100 vs 0). You can see this in the DispatcherServlet precisely the method HandlerExecutionChain getHandler(HttpServletRequest request).
In our projects we configure the access to the actuator endpoints with spring security so i'm not aware if there are any recommended ways to do it but:
The handler are chose by order so this a thing to consider, you can also try to manipulate the actuator WebMvcEndpointHandlerMapping.
Like i said i'm not sure about the right solution, but i hope it points you in the right direction to find a proper solution.
regards, WiPu

Related

Can I determine in a servlet filter whether a HttpServletRequest maps to a particular Spring controller class

I'm working on an application that's using a OncePerRequestFilter to do some custom log-like behavior using the incoming web request. This behavior uses both the HttpServletRequest & HttpServletResponse. Additionally, the filter uses both ContentCachingRequestWrapper & ContentCachingResponseWrapper to access the request/response bodies.
It's been decided that we only want to do this behavior when methods in particular Spring controllers have been called, since it's not something we want to do for other controllers/actuator endpoints/etc. Is there a way to tell whether the incoming request will be (or was) mapped to a controller?
public class ExampleFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Can I tell here whether this will be mapping to an endpoint in
// ExampleController or NestedExampleController?
ContentCachingRequestWrapper requestToUse = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response);
try {
filterChain.doFilter(requestToUse, responseToUse);
// Can I tell here whether this was mapped to an endpoint in
// ExampleController or OtherExampleController?
} finally {
responseToUse.copyBodyToResponse(); // Write the cached body back to the real response
}
}
}
#RestController
#RequestMapping("/example")
public class ExampleController {
#GetMapping("/{id}")
public Example retrieveExample() {
return getValue(); // Retrieve the value
}
// ...
}
#RestController
#RequestMapping("/example/{id}/nested")
public class NestedExampleController {
#GetMapping("/{nestedId}")
public NestedExample retrieveNestedExample() {
return getValue(); // Retrieve the value
}
// ...
}
I've dug around the Spring MVC/Boot internals a bit, and I'm not sure if there's a way to easily do this. As an alternative, I can do some manual URL pattern matching, which probably won't necessarily exactly match up to the methods in the controllers, but may get me close enough to be an acceptable solution.
To summarize: is there a way in a web filter to tell whether the incoming request will be mapped to a controller (prior to executing the filter chain) or whether it was mapped to a controller (after executing the filter chain)?
What you want is basically a cross-cutting concern that targets a specific part of your application - in this case, logging.
This is one of the most common use-cases for aspect-oriented programming, for which Spring has built-in support using AspectJ-style pointcuts.
You will need:
To enable AOP within your Spring configuration on a configuration class, as follows:
#Configuration
#EnableAspectJAutoProxy
public class AopConfiguration {
}
Define an aspect, e.g. as follows:
#Aspect
public class LoggingAspect {
Logger log = ...; // define logger
// Matches all executions in com.example.ExampleController,
// with any return value, using any parameters
#Pointcut("execution(* com.example.ExampleController.*(..))")
public void controllerExecutionPointcut() {}
#Around("controllerExecutionPointcut()")
public Object aroundTargetControllerInvocation(ProceedingJoinPoint pjp) {
log.debug("About to invoke method: {}", pjp.getSignature().getName());
try {
return pjp.proceed();
} catch (Throwable t) {
// note that getArgs() returns an Object[],
// so you may want to map it to a more readable format
log.debug("Encountered exception while invoking method with args {}", pjp.getArgs());
throw t;
}
log.debug("Sucessfully finished invocation");
}
}
See e.g. this guide to learn more about pointcut expressions.
Another common use-case for this is timing your method calls, although for that something like Micrometer (and the Micrometer adapter for Spring) using #Timed would probably be better.
You may also wish to read through the reference documentation, which devotes quite a lot of information on how AOP in Spring works.
Note: as will almost all other Spring proxying mechanisms, invocations from within the target object will not be proxied, i.e. this.otherControllerMethod() will not be subject to interception by the above advice. Similarly, private methods also cannot be intercepted. See section 5.4.3 of the reference documentation for more information.
As a last note, if performance is of great importance, you should check out AspectJ compile-time or load-time weaving, which gets rid of some of the overhead introduced by Spring's proxying mechanism (which is what Spring AOP uses under the hood). This will most likely not be necessary in your case, but is good to keep in mind.
Edit for comment:
Thanks! One caveat with this approach is that it does not give me access to the HttpServletRequest or HttpServletResponse, which is something I'm making use of. I can see where this would be helpful if that wasn't something I needed. I see that I wasn't explicit about that requirement in my question, so I'll update accordingly.
Indeed, that is unfortunately not directly possible with this approach. If you really need the request, then the HandlerInterceptor approach mentioned by #DarrenForsythe is another possible to go. If all you're going for is logging though, I see no reason why you absolutely need the request - unless you wish to extract specific headers and log those.
In that case, IMO, a OncePerRequestFilter as you originally tried would be far better, as you can control for which requests the filter gets applied (using shouldNotFilter(HttpServletRequest request) and matching on the URL).
After some additional poking around and some trial and error, I discovered that the controller is accessible through the RequestMappingHandlerMapping bean. When the request can be handled by a controller, this will map the request to a HandlerMethod for the controller's request handling method.
public class ExampleFilter extends OncePerRequestFilter {
private RequestMappingHandlerMapping requestMappingHandlerMapping;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Object handler = getHandlerBean(request);
boolean isHandledController = handler instanceof ExampleController
|| handler instanceof NestedEampleController;
if (!isHandledController) {
filterChain.doFilter(request, response);
return;
}
// ...
}
private Object getHandlerBean(HttpServletRequest request) {
try {
HandlerExecutionChain handlerChain = requestMappingHandlerMapping.getHandler(request);
if (handlerChain != null) {
Object handler = handlerChain.getHandler();
if (handler instanceof HandlerMethod) {
return ((HandlerMethod) handler).getBean();
}
}
return null;
} catch (Exception e) {
return null;
}
}
#Override
protected void initFilterBean() {
WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
requestMappingHandlerMapping = appContext.getBean(RequestMappingHandlerMapping.class);
}
}
To be extra thorough and truly mimic Spring's handler logic, the DispatcherServlet logic could be used/mimicked instead of directly referencing RequestMappingHandlerMapping. This will consult all handlers, not just the RequestMappingHandlerMapping.
public class ExampleFilter extends OncePerRequestFilter {
private DispatcherServlet dispatcherServlet;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Object handler = getHandlerBean(request);
boolean isHandledController = handler instanceof ExampleController
|| handler instanceof NestedEampleController;
if (!isHandledController) {
filterChain.doFilter(request, response);
return;
}
// ...
}
private Object getHandlerBean(HttpServletRequest request) {
try {
HandlerExecutionChain handlerChain = getHandler(request);
if (handlerChain != null) {
Object handler = handlerChain.getHandler();
if (handler instanceof HandlerMethod) {
return ((HandlerMethod) handler).getBean();
}
}
return null;
} catch (Exception e) {
return null;
}
}
/**
* Duplicates the protected "getHandler" method logic from DispatcherServlet.
*/
private HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
List<HandlerMapping> handlerMappings = dispatcherServlet.getHandlerMappings();
if (handlerMappings != null) {
for (HandlerMapping mapping : handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
#Override
protected void initFilterBean() {
WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
dispatcherServlet = appContext.getBean(DispatcherServlet.class);
}
}
I'm not sure if there is a more idiomatic approach, and it definitely feels like it's jumping through some hoops and digging into the Spring internals a bit too much. But it does appear to work, at least on spring-web 5.2.7.RELEASE.

Log URL that caused 404 in a DropWizard or Jetty application

We're running DropWizard and trying to drop in logging of the URLs that cause 404 responses to be thrown
We have a catchall exception mapper that receives a NotFoundException. Frustratingly that exception doesn't carry context of which URL caused it to be thrown.
Example application here: https://github.com/pauldambra/not.found.example
We're using an ExceptionMapper
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
logger.error(urlFrom(exception), exception);
return Response.status(404).build();
}
private String urlFrom(final NotFoundException exception) {
return "why is this not a property on the exception?!";
}
private class ExampleLogger {
void error(final String notFoundUrl, final NotFoundException exception) {
System.out.println("someone tried to load " + notFoundUrl);
System.out.println(exception.getMessage());
}
}
}
If we look at the application logs when someone requests a URL that the application doesn't serve we see that the application can log that it is returning a 404 for a path but our custom logger has no access to the URL
someone tried to load why is this not a property on the exception?!
HTTP 404 Not Found
127.0.0.1 - - [08/May/2019:09:53:47 +0000] "GET /ping/pong HTTP/1.1" 404
Is an ExceptionMapper the wrong way to do this?
Turns out there are two ways
one does use the exception mapper:
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
// magically inject a thing
// remember that magic is for evil wizards
#Context
private HttpServletRequest request;
private ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
final StringBuffer absolutePath = HttpUtils.getRequestURL(request);
logger.error("exception mapper: " + absolutePath, exception);
return Response.status(404).build();
}
}
This works but isn't very discoverable.
You can also add a response filter
public class NotFoundLoggingFilter implements ContainerResponseFilter {
private ExampleLogger logger = new ExampleLogger();
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
if (responseContext.getStatus() != 404) {
return;
}
final URI absolutePath = requestContext.getUriInfo().getAbsolutePath();
logger.error("filter: " + absolutePath, new NotFoundException());
}
}
This doesn't require any magic so suits me but you can choose your poison.
Really the path should be on the NotFoundException - if I had more time I'd propose the code change to add it.

Logging filter response with body for REST

I'm trying to log all request and response with the body for my REST service.
So far for logging request, I'm using the Spring built-in solution to log payloads RequestLoggingFilterConfig and it works perfectly.
Now I'm looking for a similar solution for logs Response.
The question is how can I logs the whole responses with the body from REST and can it be done only by the configuration file?
My configuration for the request
#Configuration
public class RequestLoggingFilterConfig {
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST DATA: ");
return filter;
}
}
and the application.properties
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
application.properties logging.level.* help just configured your logging level, For handling the logging response,
You need some kind of middleware which can intercept your response
1: Custom Filter Filter is one of the best approach for handle this problem
eg:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class WebFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(WebFilter.class);
private static final boolean CONDITION = true;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("Initiating WebFilter >> ");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(requestWrapper, response);
// log your response here
}
#Override
public void destroy() {
logger.debug("Destroying WebFilter >> ");
}
}
register your filter
#Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(someFilter());
registration.addUrlPatterns("/api/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("someFilter");
registration.setOrder(1);
return registration;
}
public Filter someFilter() {
return new WebFilter();
}
2:InterceptorAdapter
You can extend HandlerInterceptorAdapter and override the afterCompletion method
public void afterCompletion,
#Component
public class LogginInterceptor
extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// log your response here
}
}
3:AOP Also you can use magical AOP for handle the response using #After
impl

Spring boot 404 error custom error response ReST

I'm using Spring boot for hosting a REST API. Instead of having the standard error response I would like to always send a JSON response even if a browser is accessing the URL and as well a custom data structure.
I can do this with #ControllerAdvice and #ExceptionHandler for custom exceptions. But I can't find any good ways of doing this for standard and handled errors like 404 and 401.
Are there any good patterns of how to do this?
For those Spring Boot 2 users who don't wanna use #EnableWebMvc
application.properties
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
ControllerAdvice
#RestControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public HashMap<String, String> handleNoHandlerFound(NoHandlerFoundException e, WebRequest request) {
HashMap<String, String> response = new HashMap<>();
response.put("status", "fail");
response.put("message", e.getLocalizedMessage());
return response;
}
}
Source
It is worked for me in case of #RestControllerAdvice with spring boot
spring.mvc.throw-exception-if-no-handler-found=true
server.error.whitelabel.enabled=false
spring.resources.add-mappings=false
#RestControllerAdvice
public class ErrorHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND )
public String handleNotFoundError(NoHandlerFoundException ex) {
return "path does not exists";
}
}
I've provided the sample solution on how to override response for 404 case. The solution is pretty much simple and I am posting sample code but you can find more details on the original thread: Spring Boot Rest - How to configure 404 - resource not found
First: define Controller that will process error cases and override response:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Second: you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Summing up all answers and comment, I think the best way to do this is-
First, tell spring boot to throw exception in case of no handler found in application.properties
spring.mvc.throw-exception-if-no-handler-found=true
Then handle NoHandlerFoundException in your application. I handle this by following way
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
logger.error("URL not found exception: " + ex.getRequestURL());
prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
}
}
If you are using Swagger then you can view my other answer to exclude swagger URL from this exception handler
404 error is handled by DispatcherServlet. there is a property throwExceptionIfNoHandlerFound, which you can override.
In Application class you can create a new bean:
#Bean
DispatcherServlet dispatcherServlet () {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
...and then catch the NoHandlerFoundException exception in
#EnableWebMvc
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
doSomething(LOG.debug("text to log"));
}
}
You may extend the ResponseEntityExceptionHandler class, which include a lot of common exceptions in a Spring Boot Project. For example, if you wish to use a custom handler for binding exceptions, you may use the following,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"key\":\"value\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_ACCEPTABLE, request);
}
}
An other example for the http status 404-Not Found,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Regarding the 404 not found exception you should configure the DispatcherServlet to throw and exception if it doesn't find any handlers, instead of the default behavior. For issues with 404, you may also read this question.
I was having the same issue but fixed it using a different method.
To return 404, 401 and other status in a custom response, you can now add the response status to the custom exception class and call it from your exception handler.
With spring utility class AnnotationUtils, you can get the status of any of the defined custom exceptions with the findAnnotation method and it will return the appropriate status using whatever annotation you defined for the exceptions including not found.
Here's my #RestControllerAdvice
#RestControllerAdvice
public class MainExceptionHandler extends Throwable{
#ExceptionHandler(BaseException.class)
ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException e)
{
ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
if(status != null)
{
return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
}
}
CustomParamsException to return Bad request status
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
private static final String CODE = "400";
public CustomParamsException(String message) {
super(CODE, message);
}
}
Details not found to return Not Found Status
#ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
private static final String CODE = "400";
public DetailsNotException(String message) {
super(CODE, message);
}
}
A GeneralMainException to extend Excetion
public class GeneralMainException extends Exception {
private String code;
private String message;
public GeneralMainException (String message) {
super(message);
}
public GeneralMainException (String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
#Override
public String getMessage() {
return message;
}
}
You can decide to handle other system exceptions by including it to the controller advice.
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
ExceptionErrorResponse sysError(Exception e)
{
return new ExceptionErrorResponse(""1002", e.getMessage());
}
It seems that you need to introduce an appropriately annotated method, e.g. for unsupported media type (415) it will be:
#ExceptionHandler(MethodArgumentNotValidException)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
logger.error('Caught exception', e)
def response = new ExceptionResponse(
error: 'Validation error',
exception: e.class.name,
message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
path: req.servletPath,
status: BAD_REQUEST.value(),
timestamp: currentTimeMillis()
)
new ResponseEntity<>(response, BAD_REQUEST)
}
However it may not be possible since 401 and 404 may be thrown before they reach DispatcherServlet - in this case ControllerAdvice will not work.
You can add custom ErrorPage objects which correlate to the error-page definition in web.xml. Spring Boot provides an example...
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
}
}
EDIT: While I think the method above will work if you make the error pages rest controllers, an even easier way would be to include a custom ErrorController like the one below...
#Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
return new CustomErrorController(errorAttributes);
}
// ...
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#Override
#RequestMapping(value = "${error.path:/error}")
#ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
ResponseEntity<Map<String, Object>> error = super.error(request);
HttpStatus statusCode = error.getStatusCode();
switch (statusCode) {
case NOT_FOUND:
return getMyCustomNotFoundResponseEntity(request);
case UNAUTHORIZED:
return getMyCustomUnauthorizedResponseEntity(request);
default:
return error;
}
}
}
Please see Spring Boot REST service exception handling. It shows how to tell the dispatcherservlet to emit exceptions for "no route found" and then how to catch those exceptions. We (the place I work) are using this in production for our REST services right now.
Starting with Spring version 5 can use class ResponseStatusException:
#GetMapping("example")
public ResponseEntity example() {
try {
throw new MyException();
} catch (MyException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "My Exception", e);
}
}
I wanted to have the same error format (json) structure across all possible error scenarios, so I just registered my own ErrorController reusing the code from AbstractErrorController:
#Controller
#RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public class ErrorController extends AbstractErrorController {
public ErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toUnmodifiableList()));
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
final var status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
return new ResponseEntity<>(getErrorAttributes(request, ErrorAttributeOptions.defaults()), status);
}
#Override
public String getErrorPath() {
return null;
}
}
with this you dont need any controller advice, all errors go to error method by default

Spring Security ignores failed authentication

I'm in the process of setting up Spring Security. My CookieAuthenticationFilter should make sure to keep users out unless they have a cookie with an UUID we accept. Although CookieAuthenticationFilter sets an empty context if the UUID is not accepted I still have access to all URLs.
Any idea what's missing?
This is my security configuration:
#Configuration
#EnableWebMvcSecurity
public class LIRSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(cookieAuthenticationFilter())
.authorizeRequests()
.antMatchers("/**").hasAnyAuthority("ALL");
}
#Bean
public CookieAuthenticationFilter cookieAuthenticationFilter() {
return new CookieAuthenticationFilter(cookieService());
}
private CookieService cookieService() {
return new CookieService.Impl();
}
#Bean(name = "springSecurityFilterChain")
public FilterChainProxy getFilterChainProxy() {
SecurityFilterChain chain = new SecurityFilterChain() {
#Override
public boolean matches(HttpServletRequest request) {
// All goes through here
return true;
}
#Override
public List<Filter> getFilters() {
List<Filter> filters = new ArrayList<Filter>();
filters.add(cookieAuthenticationFilter());
return filters;
}
};
return new FilterChainProxy(chain);
}
}
This is the CookieAuthenticationFilter implementation:
public class CookieAuthenticationFilter extends GenericFilterBean {
#Resource
protected AuthenticationService authenticationService;
private CookieService cookieService;
public CookieAuthenticationFilter(CookieService cookieService) {
super();
this.cookieService = cookieService;
}
#Override
public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
UUID uuid = cookieService.extractUUID(request.getCookies());
UserInfo userInfo = authenticationService.findBySessionKey(uuid);
SecurityContext securityContext = null;
if (userInfo != null) {
securityContext = new CookieSecurityContext(userInfo);
SecurityContextHolder.setContext(securityContext);
} else {
securityContext = SecurityContextHolder.createEmptyContext();
}
try {
SecurityContextHolder.setContext(securityContext);
chain.doFilter(request, response);
}
finally {
// Free the thread of the context
SecurityContextHolder.clearContext();
}
}
}
The issue here is that you don't want to use GenericFilterBean as it's not actually part of the Spring Security framework, just regular Spring so it's not aware of how to send security-related messages back to the browser or deny access, etc. If you do want to use the GenericFilterBean you'll need to handle the redirect or the 401 response yourself. Alternatively, look into the AbstractPreAuthenticatedProcessingFilter that is part of the Spring Security framework. There is some documentation here: http://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html

Categories

Resources