I have a spring boot application, which makes a large number of requests to external web services. I'm using the #Cacheable annotation in many places to cache requests. I'm trying to figure out how to cache requests on a "per-request" basis. i.e:
Suppose I have the following method which calls an external service:
#Cacheable
private List<Product> listProducts(String orgCode, String channel, String userToken) {
return externalService.listProducts(orgCode, channel, userToken);
}
When a request comes in to my spring application, it calls the listProducts method 5 times. The external service is only called once, and the cached result is used for the other 4 calls.
Now another request comes in, and calls listProducts again. The previously cached result is returned. However because this is a new request to my application, I want to refresh the results.
I feel like #Cacheable has a parameter for this that I'm just not finding.
The best way is probably to create a web Filter to clear your cache. Look at #CacheEvict(allEntries=true), you can annotate another method and call it in the Filter.
The filter could look like this:
#Component
public class CacheEvictFilter implements Filter {
private final MyService myService;
public CacheEvictFilter(final MyService myService) {
this.myService = myService;
}
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException
{
chain.doFilter(request, response);
myService.evictProducts();
}
// other methods
}
The evictProducts method could look like this:
#CacheEvict(allEntries=true)
public void evictProducts() {
log.info("Evicted all products")
}
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
I have a series of Rest API Controllers in my Spring boot application with Request Mappings that match certain URLs.
I need to change my implementation to always make sure that a specific custom header is in place for all requests. If header is not there I want to fail the request. If it is I want to forward to the appropriate controller which would be the same as my current implementation.
Is there a way to do this in Spring Boot without modifying my existing controllers at all? Could I try to use something like Spring Security, even though my header is not related to security at all?
Thank you.
Web MVC defines an abstraction called "HandlerInterceptor" and its no-op implementation HandlerInterceptorAdapter
So you can register the bean that looks like this:
#Component
public class RequestProcessingTimeInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// check the headers, extract them from request, whatever
return true; // if you want to proceed to controller
return false;// otherwise :)
}
}
This will instruct spring mvc to call the method before the flow gets to the controller.
You can configure a Filter as a #Service.
#Service
#NoArgsConstructor #Log4j2
public class FilterImpl implements Filter {
#Override
public void init(FilterConfig config) throws ServletException { }
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request.getHeader("required-header-name") != null) {
chain.doFilter(request, response);
} else {
log.info("Rejected {}", request);
}
}
#Override
public void destroy() {
}
}
I added an async endpoint to a existing spring-mvc application:
#RestController
public class MyController {
#PostMapping("/")
public Mono<String> post(Object body) {
return Mono.just("test");
//webClient.retrieve().bodyToMono(String.class);
}
}
I want to create a global interceptor/filter that will log the request body payload. But how can I get access to it?
I tried adding a HandlerInterceptorAdapter, but the payload is always empty:
static class LoggingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
byte[] buf = wrapper.getContentAsByteArray();
System.out.println(buf);
System.out.println(buf.length);
return true;
}
}
Maybe the payload is not yet present in the request, or has already been read. So how can I access the body in this async case?
Unfortunately in Webflux you cannot use HandlerInterceptorAdapter because it came from web mvc module and works only with the servlets.
I found a good article with solutions.
P.S. You must to remove spring-mvc dependencies if going to use reactive endpoins.
I am working on a Spring Boot RESTful application which will be exposing a bunch of APIs for the web app to perform CRUD operations on the resources.
I am using spring-data-rest (along with spring-data-jpa of course) to expose the entities/repositories with the help of Spring Magic.
Even though I have secured (role-based) the endpoints with spring-security, it is not completely secure.
For example:
I have a User entity with has one-to-many relationship with Car. So the endpoint (auto exposed by spring-data-rest) for getting a user's cars is localhost:8080/users/{userId}/cars
However, any user with the required role can just pass the userId of another user and still access the endpoint.
The behavior I want is to secure these endpoints in a way that if I a logged-in user's ID is 1, then we can only hit localhost:8080/users/1/cars. Any other request with any other userId should end up in 403 or something.
Note: I know if write my own controllers then I can get a handle of the principal and do what I desire. I just want to know is there a way or pattern in spring-data-rest to achieve this?
Since you have already secured the application with Spring Security , here is another alternative with Method Security Expressions
Please review the #Pre and #Post Annotations for your requirement.
You may store the logged-in user's userId to the Authentication object.Details here.
Secure the required method with the #PreAuthorize annotation as follows
#PreAuthorize("#user.userId == authentication.principal.userId")
public List<Car> getCars(User user){..}
Do remember to enable method security
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {..}
To achieve that you need to write an Interceptor.It will be used under following situation:
Before sending the request to the controller
Before sending the response to the client
Before writing any Interceptor it should implement the HandlerInterceptor interface.
Three methods Interceptor supports are :
preHandle() method − Perform operations before sending the request
to the controller. This method should return true to return the
response to the client.
postHandle() method − Used to perform operations before sending
the response to the client.
afterCompletion() method − This is used to perform operations
after completing the request and response.
Code :
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String pathVariablesMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
//From this pathVariablesMap extract UserId and match with a loggedinUserId
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
) throws Exception {}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {}
}
By using a InterceptorRegistry you can register your Interceptors like below :
#Component
public class MyRegistoryConfig extends WebMvcConfigurer{
#Autowired
MyInterceptor myInterceptor ;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor );
}
}
For more Info follow this link Interceptors
EDIT : As #Ritesh suggested added that point.
You're using spring security(great :D), so it's better to create a simple filter, register it, then simply do your custom authorize in that filter.
In brief
Create a Custom filter
Get userId from the URL path
Get userId from SecurityContextHolder (Authenticated user principal)
Compare fetched userIds
Register filter in spring security config (After BasicAuthenticationFilter)
1- Create a custom filter (Pseudo-code)
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//Fetch userId from path
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getContextPath();
//..
//Fetch userId from SecurityContextHolder (User Principal)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Long userId = user.getId();
//Compare userId (fethced from path) with authenticated userId (fetched from principal)
//If comparison is ok
chain.doFilter(request, response);
//else
//throw Unauthorize
}
2- Register a filter in spring security config (After BasicAuthenticationFilter.class)
#Configuration
public class Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
}
}
With this structure when an authenticated user sends a request, the request will be first checked (Comparison between userIds) and then sent.
More information for creating a filter in spring security:
A Custom Filter in the Spring Security Filter Chain
I need to hide a specific API for requests coming form IP different to a specific one.
For instance this should work if I try to use it and my IP is 192.168.1.1, but not if my IP is 192.168.1.2.
#RequestMapping(value = "/test/{id}", method = RequestMethod.GET)
#ResponseBody
#IpRestricted
public void download(#PathVariable("id") String id) {
...
}
I read I can make it creating a specific annotation, the one I called "#IpRestricted" in this example, but than how can I proceed? There are better solution to this?
I then realized I can make it without using spring security.
I made an annotation like this:
#Retention(RetentionPolicy.RUNTIME)
public #interface IpRestricted {
}
Than I check the request IP address inside a HandlerInterceptor preHandle method:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod)handler;
if (method.getMethodAnnotation(IpRestricted.class)!=null) {
if (!request.getRemoteAddr().equals("192.168.1.1")) {
throw new UnauthorizedException("Ip not authorized");
}
}
}
[....]
}
And for the download method:
#RequestMapping(value = "/test/{id}", method = RequestMethod.GET)
#ResponseBody
#IpRestricted
public void download(#PathVariable("id") String id) {
...
}
That's it!
I think the best Spring solution available for this case is the hasIpAddress() method from Spring Security. There are many different ways to configure permissions to your services via Spring Security, and the IP-based solution is also implemented.
Here is a good example of how to set it up.