I have a Spring Boot application with REST endpoints defined like this
/usermanagement/v1/access/ldap
/usermanagement/v1/access/db
I have created a Spring Interceptor to intercept all incoming request with following pattern
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/usermanagement/v1/**");
}
RequestInterceptor
#Component
public class RequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println("This is Post Handling the request");
}
}
This interceptor works only if client accesses the complete endpoint i.e
/usermanagement/v1/access/ldap
In case a partial endpoint is accessed,
/usermanagement/v1/access
interceptor is not called and a 404 is returned to the client.
Is there a way to change this behavior? The reason I am doing this is because I don't want to expose specific endpoints but common endpoints and make internal calls to services and return result through common endpoints.
You are using the wrong method. Try using afterCompletion instead of postHandle
new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//This is called before handeling any request
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//This is called after successfully handeling a request. It will not be called in case of an exception
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//This will always be called after a request, even in case of an exception
}
}
With a request to a undefined endpoint /usermanagement/v1/access Spring will throw an exception. Therefor it never will enter postHandle.
Related
I am currently trying to figure out the best way to handle errors in a spring boot app.
Assume that the app consists of thymeleaf templates served on paths starting with /admin, and a REST API served on other URIs.
My project currently has the following configuration:
Thymeleaf error templates located at /src/main/resources/templates/error/{id}.html, defined for errors 400, 401, 403, 404, 500.
Default thymeleaf error template /src/main/resources/templates/error.html
HttpSecurity configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
(...)
http
.anonymous();
http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()))
.accessDeniedHandler(new AccessDeniedHandlerImpl());
(...)
}
Exception Handlers for both rest and MVC:
#ControllerAdvice
public class ExceptionHandlers {
private final BasicErrorController basicErrorController;
public ExceptionHandlers(BasicErrorController basicErrorController) {
this.basicErrorController = basicErrorController;
}
#ExceptionHandler(Exception.class)
public Object handleAllExceptions(Exception e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(EntityNotFoundException.class)
public Object handleEntityNotFoundException(EntityNotFoundException e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.NOT_FOUND, I18nCodes.ENTITY_NOT_FOUND);
}
/**
* We exclude all exceptions deriving from {#link AccessDeniedException} from custom exception handling.
*/
#ExceptionHandler(AccessDeniedException.class)
public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
/**
* We exclude all exceptions deriving from {#link AppBaseException} from custom exception handling.
*/
#ExceptionHandler(AppBaseException.class)
public Object handleAppBaseException(AppBaseException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR, I18nCodes.getCodeByStatus(status));
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status, String message) {
String header = request.getHeader("Accept");
if (header != null && header.contains("text/html")) {
setErrorCode(request, response, status);
return basicErrorController.errorHtml(request, response);
}
return createJsonResponse(message, status, request.getRequestURI());
}
private ResponseEntity<ErrorResponseDTO> createJsonResponse(String message, HttpStatus status, String path) {
ErrorResponseDTO errorResponseDTO = new ErrorResponseDTO()
.setTimestamp(new Timestamp(System.currentTimeMillis()))
.setStatus(status.value())
.setMessage(message)
.setPath(path)
.setError(status.name().toLowerCase());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(httpHeaders).body(errorResponseDTO);
}
private void setErrorCode(HttpServletRequest request, HttpServletResponse response, HttpStatus httpStatus) {
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, httpStatus.value());
response.setStatus(httpStatus.value());
}
}
Without the Exception handlers everything works about as expected, I get 401 error when I am not authenticated, and 403 when authenticated but lacking authorities. My custom exceptions are correctly mapped according to their message and Status defined in #ResponseStatus eg:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class AccountInfoException extends AppBaseException {
protected AccountInfoException(String message) {
super(message);
}
protected AccountInfoException(String message, Throwable cause) {
super(message, cause);
}
public static AccountInfoException emailAlreadyExists() {
return new AccountInfoException(I18nCodes.EMAIL_EXIST);
}
public static AccountInfoException accountNotFound() {
return new AccountInfoException(I18nCodes.ACCOUNT_NOT_FOUND);
}
}
Will map to status 400 with one of two messages I18nCodes.EMAIL_EXIST or I18nCodes.ACCOUNT_NOT_FOUND.
I arrive on the correct thymeleaf error page depending on the status code - life is perfect.
The issue comes with the requirement of handling all other exceptions. It's not a possibility to let an unexpected error to the client. So for that I've defined an exception handler handing Exception, to return a generic 500 message. This breaks everything. I'm unable to access the Status codes of my custom exceptions
Authentication entry point and access denied handler are both ignored, and both cases of being not authenticated and having insufficient authority are handles in the same exception(AccessDeniedException). I also lose default mappings for some exceptions eg. org.springframework.security.authentication.LockedException returns status 401.
My temporary workaround was to handle all exceptions, and to create methods handling exceptions that I want to ignore and just rethrow the exception. I feel like the best thing I could do is remove all exception handlers and repack all container exceptions to my own custom exceptions, only issue is that there doesn't seem to be a way to do it.
I'm looking for a more permanent solution - what would be the best approach?
I'm using Spring boot version 2.0.5.RELEASE in my application. I have got more than 500 Restful APIs in the application. To these APIs, a new request header needs to be added. Is there a way to add header in one place and can be used by all the 500 APIs?
Yes you can write an interceptor for every request at root level and append your headers to that request.You can use prehandle as
This is used to perform operations before sending the request to the
controller.
Below is the code snippet
#Component
public class ProductServiceInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {}
}
#RestController
#RequestMapping("/headerRequestPath)
Add this at the begining of your code file. That way all path's will be appended with header 'headerRequestPath'
I have a public method annotated with #Async. This method is not in the controller. I want to intercept the thread after completion of the thread execution so that I can clear certain ThreadLocals.
I already looked into AsyncHandlerInterceptor but this gets hit when the controller receives the request and not before and after the #Async execution.
public class SampleAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
#Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("afterConcurrentHandlingStarted " + request.getRequestURI());
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle " + request.getRequestURI());
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle " + request.getRequestURI());
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion " + request.getRequestURI());
}
}
What I would like is an interceptor like CallableProcessingInterceptor, but for #Async operation.
AsyncHandlerInterceptor is for web requests, #Async is general annotation which allows to delegate method execution to thread pool. No surprise these do not work well together.
#Async allows to provide name of thread pool. You can create your own pool which wraps all submitted tasks into your class so later you will intercept all operation with task.
One more question about spring configuration...
I have several rest methods opened to everyone and not secured. These rest methods on server #1 are used by another server #2 in the same domain to get some data. The idea is that server #2 sets my_super_secure_cookie to some secure token and server #1 decodes and verifies it. Here is the code:
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// Some code
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/rest/public/*").permitAll()
.anyRequest().authenticated();
}
// More code
}
public class SuperSecurityFilter extends FilterSecurityInterceptor implements Filter {
public SuperSecurityFilter(String key) {
super(key);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Cookie[] cookies = req.getCookies();
Optional<Cookie> tokenCookie = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals("my_super_secure_cookie")).findFirst();
if (tokenCookie.isPresent()) {
Cookie cookie = tokenCookie.get();
TokenCookie.create(cookie.getValue()).validate();
} else {
throw new Exception("Ooops!"));
}
chain.doFilter(req, res);
}
}
The question is how do I configure SecurityConfig to use SecurityTokenFilter on request to any of the /rest/public/* rest methods. Something like:
http
.antMatcher("/rest/public/*")
.addFilterBefore(new SuperSecurityFilter());
is not working, SuperSecurityFilter is not called on request.
p.s. I'm forced to work with this type of security model due to current business logic restrictions.
I solved (applied workaround?) the issue I have by implementing not filter, but interceptor, like this:
public class SuperSecurityInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// implementation here
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// Nothing here
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Nothing here
}
}
And registered this interceptor in my entity extending WebMvcConfigurerAdapter. Like this:
registry.addInterceptor(new SupeSecurityInterceptor()).addPathPatterns("/rest/public/*");
Not sure if this is right thing to do though... Anyway would be glad to know about the conventional approach of implementing this type of functionality.
I'd like to run some code (for logging, or custom security, etc) before and/or after spring calls the controller method. I know Filters can be created to operate on ServletRequests and ServletResponses, but it's not clear how to access headers, body, query parameters, etc from those.
Most similar to what I'm trying to do is action composition in Play Framework for java.
Is there a way to do this in Spring?
Here is an example of how to inject a Header for every request using a Filter
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public final class VersionFilter implements Filter {
#Value("${version}")
protected String version;
protected FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse responseAddition = (HttpServletResponse) response;
responseAddition.addHeader("X-Application-Version", this.version);
chain.doFilter(request,responseAddition);
}
#Override
public void destroy() {
}
}