I'm having trouble adding a custom Header to a RestTemplate using AOP in Spring.
What I have in mind is having some advice that will automatically modify execution of RestTemplate.execute(..) by adding this one Header. Other concern is targeting a particular RestTemplate instance that is a member of a Service which requires having this Header passed.
Here is the code of my Advice as it looks like now:
package com.my.app.web.controller.helper;
import com.my.app.web.HeaderContext;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
#Aspect
public class MyAppHeaderAspect {
private static final String HEADER_NAME = "X-Header-Name";
#Autowired
HeaderContext headerContext;
#Before("withinServiceApiPointcut() && executionOfRestTemplateExecuteMethodPointcut()")
public void addHeader(RequestCallback requestCallback){
if(requestCallback != null){
String header = headerContext.getHeader();
}
}
#Pointcut("within (com.my.app.service.NeedsHeaderService)")
public void withinServiceApiPointcut() {}
#Pointcut("execution (* org.springframework.web.client.RestTemplate.execute(..)) && args(requestCallback)")
public void executionOfRestTemplateExecuteMethodPointcut(RequestCallback requestCallback) {}
}
The problem that I'm having is how to modify RequestCallback to add my header. Being an interface it looks rather empty, on the other hand I'd rather not use a concrete implemntation because then I'd have to manually check if the implementation matches expected class. I'm beginning to wonder if this is really a correct method to do this. I found this answer Add my custom http header to Spring RestTemplate request / extend RestTemplate
But it uses RestTemplate.exchange() when I've checked that on my execution path RestTemplate.execute() is being used. Anyone has any ideas here?
Related
I have really poor experience with ribbon/eureka so forgive me if this is a stupid question:
I have two different microservice both connected to a discovery server, the first one calls the second using a custom annotation that sends a request using rest template.
Custom annotation name is PreHasAuthority
Controller :
#PreHasAuthority(value="[0].getProject()+'.requirements.update'")
#PostMapping(CREATE_UPDATE_REQUIREMENT)
public ResponseEntity<?> createUpdateRequirement(#Valid #RequestBody RequirementDTO requirementDTO
, HttpServletRequest request, HttpServletResponse response) {
return requirementService.createUpdateRequirement(requirementDTO, request, response);
}
Annotation interface :
import java.lang.annotation.*;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PreHasAuthority {
String value();
}
Annotation implementation:
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import netcomgroup.eu.service.AuthenticationService;
#Aspect
#Component
public class PreHasAuthorityServiceAspect {
#Autowired
private AuthenticationService authenticationService;
#Around(value = "#annotation(PreHasAuthority)")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
PreHasAuthority preHasAuthority = method.getAnnotation(PreHasAuthority.class);
Object[] args = joinPoint.getArgs();
String permission = preHasAuthority.value();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(permission);
String per = (String) expression.getValue(args);
String token =null;
for(Object o : args) {
if(o instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest)o;
token=request.getHeader("X-Auth");
break;
}
}
if(token==null) {
throw new IllegalArgumentException("Token not found");
}
boolean hasPerm = authenticationService.checkPermission(per,token);
if(!hasPerm)
throw new Exception("Not Authorized");
}
}
My Ribbon configuration
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
public class RibbonConfiguration {
#Autowired
IClientConfig config;
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RoundRobinRule();
}
}
Eureka config in application properties
#Eureka config
eureka.client.serviceUrl.defaultZone= http://${registry.host:localhost}:${registry.port:8761}/eureka/
eureka.client.healthcheck.enabled= true
eureka.instance.leaseRenewalIntervalInSeconds= 10
eureka.instance.leaseExpirationDurationInSeconds= 10
by calling the api from postman request is sendend correctly to the second microservice and i'm certain the return is "true".
After that the request stops before entering the createUpdateRequirement method and returns '1' as postman body response. No error of sort is provided.
My guess is that the problem resides within the custom annotation, cause when i remove the annotation the api call works perfectly, but i cannot understand the problem as it seems all setted up correctly to me.
Your #Around advice never calls joinPoint.proceed(). Hence, the intercepted target method will never be executed.
The second problem is that your advice method returns void, i.e. it will never match any method returning another type such as the ResponseEntity<?> createUpdateRequirement(..) method.
Besides, around is a reserved keyword in native AspectJ syntax. Even though it might work in annotation-driven syntax, you ought to rename your advice method to something else like aroundAdvice or interceptPreHasAuthority - whatever.
Please do read an AspectJ or Spring AOP tutorial, especially the Spring manual's AOP chapter. 😉
This is my current dummy POST login function. I want to read the request headers inside public Response loginPost(). Is it possible? I tried for example changing the function arguments but I always get io.swagger.api.impl.LoginApiServiceImpl is not abstract and does not override abstract method loginPost(io.swagger.model.LoginPostRequestBody,javax.ws.rs.core.SecurityContext) in io.swagger.api.LoginApiService
package io.swagger.api.impl;
import io.swagger.api.*;
import io.swagger.model.*;
import io.swagger.model.LoginPost200Response;
import io.swagger.model.LoginPostRequestBody;
import java.util.Map;
import java.util.List;
import io.swagger.api.NotFoundException;
import java.io.InputStream;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.validation.constraints.*;
#javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaJerseyServerCodegen", date = "2020-01-21T12:31:41.554Z[GMT]")public class LoginApiServiceImpl extends LoginApiService {
#Override
public Response loginPost(LoginPostRequestBody body, SecurityContext securityContext) throws NotFoundException {
// do some magic!
}
}
The http headers cannot be accessed by this auto-generated class. Instead one has to go to src/gen/java/io/swagger/api/myApi.java and do the following
import javax.ws.rs.core.HttpHeaders;
edit the response function at the last part of the file so that it
also takes this argument #Context HttpHeaders requestHeaders
change the exception code at the end, the function there also
needs to take the above argument (without #Context in this case)
Then update the myApiService.java file in the same folder and of course the myApiServiceImpl.java file in src/main/java/io/swagger/api/impl/ so that they both import and take as argument the javax.ws.rs.core.HttpHeaders. Do not use #Context in these latter cases either.
The general idea is to first change the myApi.java file so that it passes request headers and then update all the files that use the request function (if you can't figure them all out, compiler errors will guide you)
The solution by Tasos is correct. However, modifying generated code is not my cup of tea. Therefore I would suggest modifying the Mustache templates:
API template and API service template (and the 'Impl' template, which is should follow suit).
I would suggest replacing SecurityContext securityContext in the service with a new container object storing all #Context objects that are needed.
E.g.:
// ... snippet from apiService.mustache ...
public abstract class {{classname}}Service {
public static class Context {
private final UriInfo uriInfo;
private final SecurityContext securityContext;
public Context(HttpHeaders httpHeaders, SecurityContext securityContext) {
this.httpHeaders = httpHeaders;
this.securityContext = securityContext;
}
// ... getters
}
{{#operation}}
public abstract Response {{nickname}}({{#allParams}}{{>serviceQueryParams}}{{>servicePathParams}}{{>serviceHeaderParams}}{{>serviceBodyParams}}{{>serviceFormParams}},{{/allParams}}Context context) throws NotFoundException;
{{/operation}}
}
{{/operations}}
The api.mustache file can be modified accordingly to fill the Context before calling the delegate method.
public Response {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}},{{/allParams}}#Context SecurityContext securityContext,
#Context HttpHeaders httpHeaders) throws NotFoundException {
var context = new {{classname}}Service.Context(httpHeaders, securityContext);
return delegate.{{nickname}}({{#allParams}}{{#isFormParam}}{{#isFile}}{{paramName}}Bodypart{{/isFile}}{{/isFormParam}}{{^isFile}}{{paramName}}{{/isFile}}{{^isFormParam}}{{#isFile}}{{paramName}}{{/isFile}}{{/isFormParam}}, {{/allParams}}context);
}
The approach above allows the implementations that use the generated code to be consistent and require no modification after code generation.
Spring has htmlEscape to escape potentially unsafe HTML from user input. I'd like to apply htmlEscape as an annotation to the backend Spring based code (similar concept). This is to help prevent XSS attacks.
class MyCreateRequestDoc {
#NotBlank(message = 'A name must be specified')
#AsSafeHtml
String name
I believe this calls for an AOP pattern, but I don't know how to implement such a thing in Java Spring.
I started with:
package com.my.company.services..security
import groovy.util.logging.Slf4j
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.springframework.stereotype.Component
import org.springframework.web.util.HtmlUtils
#Aspect
#Slf4j
#Component
class AsSafeHtmlAspect {
#Around("#within(com.my.company.services.security.AsSafeHtml)")
void assertProperty(ProceedingJoinPoint pjp){
var input = 'get this from somewhere'
return toSafeHtml(input) //return?
}
private String toSafeHtml(input) {
return HtmlUtils.htmlEscape(input)
}
}
And the related annotation used in Around above:
package com.my.company.services.security
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
#Retention(RetentionPolicy.RUNTIME)
#interface AsSafeHtml {
}
package com.test.model.listener;
import org.osgi.service.component.annotations.Component;
import com.google.gson.InstanceCreator;
import com.liferay.portal.kernel.exception.ModelListenerException;
import com.liferay.portal.kernel.model.BaseModelListener;
import com.liferay.portal.kernel.model.ModelListener;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.model.*;
#Component(immediate = true, service = ModelListener.class)
public class InsertInstanceModelListener extends BaseModelListener<Instance??> {
#Override
public void onAfterCreate(Instance?? model) throws ModelListenerException {
System.out.println("InsertInstanceModelListener.onAfterCreate()");
super.onAfterCreate(model);
}
}
I'm newbie of liferay.I think it's may be something like this,but don't know how to make it right.
You are on your way. This OSGi component needs to be specific,
you are probably looking for com.liferay.portal.kernel.model.VirtualHost
If I understood correctly.
You do not need to call supper though.
Would like to create the below hierarchical structure in REST with JAXRS and jersey as provider
#POST /origination/customers/
#PUT /origination/customers
#GET /origination/customers/{customerId}
#POST /origination/customers/{customerId}/inventory
#PUT /origination/customers/{customerId}/inventory
#GET /origination/customers/{customerId}/inventory/inventoryId
Currently all the services are written in a single class OriginationService, but for better encapsulation I would like to know if the services can be refractored like the customer origination in a seperate class called CustomerOriginationService and Inventory origination inside CustomerInventoryService (This is an example scenario, my problem is something similar)
Is it possible to achieve the above with JAXRS(Jersey) annotation
Definitely! Ant that's the standard way to assemble set of HTTP methods in different classes. You need to use #Path Example - #Path("/{parameter}").
Below code may be useful to you -
Controller Interface -
package com.teducate.api;
import java.io.UnsupportedEncodingException;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public interface TekEvents {
#GET
#Path("/{parameter}")
#Produces("application/json")
Response responseMsg( #PathParam("parameter") String parameter,
#DefaultValue("Nothing to say") #QueryParam("value") String value) throws UnsupportedEncodingException;
}
Implementation -
package com.teducate.api.impl;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import com.teducate.api.TekEvents;
import com.teducate.bo.TekEventsBO;
import com.teducate.bo.impl.TekEventBOImpl;
#Path("events")
public class TekEventsController implements TekEvents {
TekEventsBO tekEventsBO;
public TekEventsController() {
tekEventsBO = new TekEventBOImpl();
}
public Response responseMsg(String parameter, String value) {
String output = tekEventsBO.responseMsg(parameter, value);
return Response.status(200).entity(output).build();
}
}
Sub-resource locator is the keyword I was looking for.
The below article sums it up nicely
http://docs.oracle.com/javaee/6/tutorial/doc/gknav.html#gkrhr