Adding soap header for each service request - java

First I will explain the project. It is an integration Web Service, that listens for requests and internally consumes another web Service and handles its reponses in order to get the desired answer. I need to add a custom header to each request to the "internal" Web Service. I have checked that using the next configuration, the handler is a singleton class. I need that every new request to the "internal Service" creates a new instance. I have checked that sometimes the headers for request "A" are using the values for request "B". I am initializing the values of the header before the first "internal" request (I need to make a call to a method of the "internal" web service without any soap header, and then set it up using a value contained in the first response). Any ideas how can I make this work?
Thanks
//ConfigurationClass
#Bean(name = "internalService")
#Scope(scopeName="prototype")
public JaxWsPortProxyFactoryBean internalService() {
JaxWsPortProxyFactoryBean bean = new JaxWsPortProxyFactoryBean();
try {
bean.setServiceInterface(internalService.class);
bean.setWsdlDocumentUrl(new URL("https://localhost/internalService.svc?wsdl" ));
bean.setNamespaceUri( "http://schemas.internalService.com/2019/04/");
bean.setServiceName("InternalService");
bean.setPortName("InternalServicePort");
bean.setEndpointAddress("https://localhost/internalService.svc");
bean.setHandlerResolver(wsHandlerResolver());
} catch (MalformedURLException e) {
e.printStackTrace();
}
return bean;
}
#Bean(name = "wsHandlerResolver")
public WebServiceHandlerResolver wsHandlerResolver() {
WebServiceHandlerResolver wshandlerResolver = new WebServiceHandlerResolver();
List handlers = new ArrayList();
handlers.add(webServiceHandler());
wshandlerResolver.setHandlers(handlers);
return wshandlerResolver;
}
#Bean(name = "webServiceHandler")
public WebServiceHandler webServiceHandler() {
WebServiceHandler webServiceHandler = new WebServiceHandler();
return webServiceHandler;
}
//HandlerResolver class
public class WebServiceHandlerResolver implements HandlerResolver {
private List<Handler> handlers;
public List<Handler> getHandlerChain(PortInfo portInfo) {
return handlers;
}
public void setHandlers(List<Handler> handlers) {
this.handlers = handlers;
}
}
//Handler class
public class WebServiceHandler implements SOAPHandler<SOAPMessageContext> {
private String user;
private String pass;
private String source;
#Override
public boolean handleMessage(SOAPMessageContext context) {
//THIS IS WHERE I ADD THE VALUES
}
}

Use java.lang.ThreadLocal<YourPropertiesHolder> variable inside your WebServiceHandler to store and access properties. Then properties from different requests of your service will not conflict.
class YourPropertiesHolder {
String user;
String pass;
String source;
}

Finally I was able to do it. It was just a problem with the scope request in the Handler class, I was missing the proxyMode option.
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)

Related

Redirect to a POST request from one controller to another controller Spring Boot

I have a springboot project with 2 controller files as below:
File1.java
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
// need to go to POST request in another controller
}
return "not found";
}
File2.java
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return "found it";
}
return "not found 2";
}
I have tried adding java HttpURLConnection lines to send a POST request in File1.java but it does not perform the operations within testMap2, instead it exits with not found
Could you please give some suggestions on how I could accomplish this?
You could use RestTemplate to create another POST request, although I strongly suggest avoiding that.
Since both of these controllers are in the same project, try extracting the common logic into a #Service which should be injected in both controllers.
For example:
File1.java
#RestController
public class MyFirstController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MyFirstController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
return myBusinessLogic.doSomething(s);
}
return "not found";
}
}
File2.java:
#RestController
public class MySecondController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MySecondController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return myBusinessLogic.doSomething(s);
}
return "not found 2";
}
}
Finally create a service for the common logic:
#Service
public class MyBusinessLogic {
public String doSomething(String s) {
// common logic goes here
}
}
You can use RestTemplate.
Lets say our controller looks like this:
#RestController
#RequestMapping(value = "first/")
public class FirstRestController {
#PostMapping("test")
public String getTest(String s){
return service.doSomething(s);
}
}
Basically, add this method as a bean in one of your config classes. #Bean puts the method in application context. Now we can inject this method in our services.
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Now, one of our service methods in Second App, we must call the endpoint of First.
#Service
public class SecondAppService{
#Autowired
private RestTemplate restTemplate;
public String callFirst() {
final URI uri =UriComponentsBuilder.fromUriString(PATH+"first/").toUri();
restTemplate.postForEntity(uri, "something", String.class);
// check your resttemplate docs, i used postForEntity here.
// if necessery return something with response, this method expects the return string but you get the idea.
}
}
This should work.

Spring ContollerAdvice does not add body to returned error

I am new to spring and I am trying to figure out global exception handling. What I am trying to achieve here is when a request is made with an unexisting primary key, I want to return an HTTP_NO_CONTENT with the body including timestamp and given id of the said request.
Here is my Controller Advice
#ControllerAdvice
public class ControllerAdvisor {
#ExceptionHandler(NoLevelFoundException.class)
public ResponseEntity<ResponseBody> handleNoLevelFoundException( NoLevelFoundException ex) {
ex.printStackTrace();
ResponseBody error = new ResponseBody();
error.setTime(Timestamp.from(Instant.now()));
error.setMessage(ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.NO_CONTENT);
}
}
And these are my custom exception and respondBody
public class NoLevelFoundException extends RuntimeException{
public NoLevelFoundException(int id) {
super("No level with id " + id + " found!");
}
}
public class ResponseBody {
private Timestamp time;
private String message;
...
}
When I make a request to an unexisting item via postman I receive this.
With return ResponseEntity.noContent().build(); I still get the right status code but I could not find any way to add body.
I also tried this piece of code
ResponseBody error = new ResponseBody();
error.setTime(Timestamp.from(Instant.now()));
error.setMessage(ex.getMessage());
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(error);
With this style, I explicitly add body yet the outcome is still same. Right HTTP status but empty body.
#EDIT
This is how I throw my error in the first place
first request is captured by RestContorller
#RestController
#RequestMapping("/levels")
public class LevelRestApi {
private ServiceLayer service;
#Autowired
public LevelRestApi(ServiceLayer service, LevelRepository repo) {
this.service = service;
}
#GetMapping("/{stage}")
public Level getLevel(#PathVariable int stage){
return service.getLevel(stage);
}
}
calls service layer which checks if the item exists or not. I throw the error in here.
#Service
public class AlienInvadersServiceLayer implements ServiceLayer {
JpaRepository levelRepository;
#Autowired
public AlienInvadersServiceLayer(#Qualifier(value = "levelRepository") JpaRepository levelRepository) {
this.levelRepository = levelRepository;
}
#Override
public Level getLevel(int levelId) {
Optional<Level> result = levelRepository.findById(levelId);
if (result.isPresent()){
return result.get();
}
else {
throw new NoLevelFoundException(levelId);
}
}
}
Problem is with the return new ResponseEntity<>(error, HttpStatus.NO_CONTENT);
of handleNoLevelFoundException method.
When you say NO_CONTENT it just empty your response body and does makes sense.
I would suggest use HttpStatus.NOT_FOUND instead.
So your code should looks like this
#ExceptionHandler(NoLevelFoundException.class)
public ResponseEntity<ResponseBody> handleNoLevelFoundException( NoLevelFoundException ex) {
// other code
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}

How to write custom validation in rest api?

In Spring boot.
I want to do field validation and return an error if the input does not exist in the database.
I am trying to write the custom annotation for multiple input fields.
The controller is as below
#RestController
#Api(description = "The Mailer controller which provides send email functionality")
#Validated
public class SendMailController {
#Autowired
public SendMailService sendemailService;
org.slf4j.Logger logger = LoggerFactory.getLogger(SendMailService.class);
#RequestMapping(method = RequestMethod.POST, value = "/sendMail", consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {"text/xml", "application/json"})
#ResponseBody
#Async(value = "threadPoolTaskExecutor")
#ApiOperation("The main service operation which sends one mail to one or may recipient as per the configurations in the request body")
public Future<SendMailResult> sendMail(#ApiParam("Contains the mail content and configurations to be used for sending mail") #Valid #RequestBody MailMessage message) throws InterruptedException {
SendMailResult results = new SendMailResult();
try {
sendemailService.sendMessages(message);
long txnid = sendemailService.createAudit (message);
results.setTxnid (txnid);
results.setStatus("SUCCESS");
} catch(MessagingException | EmailServiceException e) {
logger.error("Exception while processing sendMail " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
} catch(Exception e) {
logger.error("Something went wrong " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
}
return new AsyncResult<SendMailResult>(results);
}
}
one DTO that is mapped with request
public class MailContext {
#NotNull
private String clientId;
#NotNull
private String consumer;
public int getClientId() {
return Integer.parseInt(clientId);
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String writeValueAsString = mapper.writeValueAsString(this);
return writeValueAsString;
}
}
Request xml
<mailMessage>
<mailContext>
<clientId>10018</clientId>
<consumer>1</consumer>
</mailContext>
</mailMessage>
I want to write a custom annotation to validate client which exists in the database (table client_tbl) if provided in the request.
consumer: is present in database table cunsumer_tbl
if these not present in database send error message else call service method.
Please suggest how to write such custom annotation with the error.
I know another way to validate this.
Inside your controller, you can register a validator.
#InitBinder
public void setup(WebDataBinder webDataBinder) {
webDataBinder.addValidators(dtoValidator);
}
Where dtoValidator is an instance of Spring Bean, for example, which must implements org.springframework.validation.Validator.
So, you just have to implement two methods: supports() and validate(Object target, Errors errors);
Inside supports() method you can do whatever you want to decide whether the object should be validated by this validator or not. (for example, you can create an interface WithClientIdDto and if the tested object isAssignableFrom() this interface you can do this validation. Or you can check your custom annotation is presented on any field using reflection)
For example: (AuthDtoValidator.class)
#Override
public boolean supports(Class<?> clazz) {
return AuthDto.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
final AuthDto dto = (AuthDto) target;
final String phone = dto.getPhone();
if (StringUtils.isEmpty(phone) && StringUtils.isEmpty(dto.getEmail())) {
errors.rejectValue("email", "", "The phone or the email should be defined!");
errors.rejectValue("phone", "", "The phone or the email should be defined!");
}
if (!StringUtils.isEmpty(phone)) {
validatePhone(errors, phone);
}
}
UPDATE:
You can do that.
Create an annotation
for example:
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = ClientIdValidator.class)
#Documented
public #interface ClientId {
String message() default "{some msg}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and implement this validator:
class ClientIdValidator implements ConstraintValidator<ClientId, Long> {
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
//validation logc
}
}
More details you can find here: https://reflectoring.io/bean-validation-with-spring-boot/

Access URITemplate or RequestLine value in Feign RequestInterceptor / RequestTemplate

I'm developing an app against a cloud application that has hard api rate limits in place. In order to have my team get a feeling for how close we are in regards to those limits I want to count all API calls made from our app in a meaningful way.
We use Feign as access layer, and I was hoping to be able to use the RequestInterceptor to count the different API endpoints we call:
RequestInterceptor ri = rq -> addStatistics(rq.url());
Now this does not work, as the resulting URLs almost always count "1" afterwards, as they already contain all resolved path variables, so I get counts for
1 - /something/id1valueverycryptic/get
1 - /something/anothercrypticidkey/get
and so on.
I was hoping to somehow get access to either the #ResuqestLine mapping value (GET /something/{id}/get) or at least the uri template pre-resolve (/somethine/{id}/get)
Is there a way to do this?
Thanks!
Maybe you could try using custom feign InvocationHandlerFactory.
I've managed to log RequestInterceptor using code like this:
change EnableFeignClients and add defaultConfiguration
#EnableFeignClients(defaultConfiguration = FeignConfig.class)
add default feign config
#Configuration
public class FeignConfig {
#Bean
#ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
#Bean
#Scope("prototype")
#ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder()
.retryer(retryer)
.invocationHandlerFactory((target, dispatch) -> new CountingFeignInvocationHandler(target, dispatch));
}
}
create your invocation handler (code based on feign.ReflectiveFeign.FeignInvocationHandler)
public class CountingFeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
public CountingFeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
RequestLine requestLine = method.getAnnotation(RequestLine.class);
addStatistics(requestLine.value());
return dispatch.get(method).invoke(args);
}
#Override
public boolean equals(Object obj) {
if (obj instanceof CountingFeignInvocationHandler) {
CountingFeignInvocationHandler other = (CountingFeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
#Override
public int hashCode() {
return target.hashCode();
}
#Override
public String toString() {
return target.toString();
}
}
Be careful and check if you feign configuration wasn't more complex and in that case extend classes as needed.
If you are using spring-cloud-starter-openfeign , You could do something like below
add the a primary contract bean
#Bean("YourContract")
#Primary
public Contract springpringContract() {
return (targetType) -> {
List<MethodMetadata> parseAndValidatateMetadata = new SpringMvcContract().parseAndValidatateMetadata(targetType);
parseAndValidatateMetadata.forEach(metadata -> {
RequestTemplate template = metadata.template();
template.header("unresolved_uri", template.path().replace("{", "[").replace("}", "]"));
});
return parseAndValidatateMetadata;
};
}
Add the contract to the feign client builder
#Bean
public <T> T feignBuilder(Class<T> feignInterface, String targetURL) {
return Feign.builder().client(getClient())
.contract(contract)
.
.
}
Once you are done with the above you should be able to access the unresolved path in the RequestTemplate
#component
public class FeignRequestFilter implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
String unresolvedUri = template.headers().getOrDefault("unresolved_uri", Collections.singleton(template.path()))
.iterator().next();
}
}
Maybe you could try overwriting feign Logger.
Suppose we have a feign client,
#FeignClient(name = "demo-client", url = "http://localhost:8080/api", configuration = FeignConfig.class)
public interface DemoClient {
#GetMapping(value = "/test/{id}")
void test(#PathVariable(name = "id") Integer id) {
}
}
import feign.Logger;
import feign.Request;
import feign.Response;
import java.io.IOException;
public class CustomFeignRequestLogging extends Logger {
#Override
protected void logRequest(String configKey, Level logLevel, Request request) {
super.logRequest(configKey, logLevel, request);
// targetUrl = http://localhost:8080/api
String targetUrl = request.requestTemplate().feignTarget().url();
// path = /test/{id}
String path = request.requestTemplate().methodMetadata().template().path();
}
}

Jax RS Authorization

I have an existing code at a class which is extended from javax.ws.rs.core.Application
...
Context childContext = component.getContext().createChildContext();
JaxRsApplication application = new JaxRsApplication(childContext);
application.add(this);
application.setStatusService(new ErrorStatusService());
childContext.getAttributes().put("My Server", this);
...
ChallengeAuthenticator challengeGuard = new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "REST API Realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(null, "user"));
User owner = new User("admin", "admin");
realm.getUsers().add(owner);
realm.map(owner, Role.get(null, "admin"));
//Attach verifier to check authentication and enroler to determine roles
challengeGuard.setVerifier(realm.getVerifier());
challengeGuard.setEnroler(realm.getEnroler());
challengeGuard.setNext(application);
// Attach the application with HTTP basic authentication security
component.getDefaultHost().attach(challengeGuard);
I don't have a web.xml at my code. I would like to add authorization to my code. This: https://restlet.com/technical-resources/restlet-framework/guide/2.3/core/security/authorization does not apply to me since I don't have restlet resources.
How can I implement jax rs authorization into my code?
EDIT 1: Existing code uses restlet JAX-RS extension: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs
I've tried that at my jax-rs resource class:
#GET
#Path("/")
public String getStatus() {
if (!securityContext.isUserInRole("admin")) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
...
}
However, it throws 403 even I log in with admin user.
EDIT 2:
When I check here: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs There is a piece of code:
this.setRoleChecker(...); // if needed
This may solve my issue but I don't know how to set a role checker.
PS: I use jersey 1.9 and restlet 2.2.3.
It's not really clear (at least to me :-) ) what you are trying to achieve.
If you have a class which is a subclass of javax.ws.rs.core.Application, you should be able to simply add #RolesAllowed("user") as an annotation to your resource classes, as shown in https://jersey.java.net/documentation/latest/security.html
#Path("/")
#PermitAll
public class Resource {
#RolesAllowed("user")
#GET
public String get() { return "GET"; }
#RolesAllowed("admin")
#POST
public String post(String content) { return content; }
#Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}
Accessing that resource should prompt you for your credentials. If that doesn't work, then you need to provide a small code sample, which compiles and doesn't do what you want it to do. Then it's easier to see where the problem is and what needs to be done to make it work
I could make it work like that:
Application class:
...
application.setRoles(getRoles(application));
...
public static List<Role> getRoles(JaxRsApplication application) {
List<Role> roles = new ArrayList<>();
for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
roles.add(new Role(application, authorizationRole.toString()));
}
return roles;
}
...
Authorization enum:
public enum AuthorizationRoleEnum {
USER("user"),
ADMIN("admin");
private final String value;
AuthorizationRoleEnum(String value) {
this.value = value;
}
#Override
public String toString() {
return value;
}
}
At my resource classes:
...
#Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
if (securityContext.getAuthenticationScheme() != null
&& !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
}
}
...
You need to implement your RoleChecker using this interface.
As the doc says:
Because the Restlet API does not support its own mechanism for role checks (as e.g. the Servlet API), you must use this inteface if you need role checks in a JAX-RS application.
This interface is used to check, if a user is in a role. Implementations must be thread save.
so as an example of implementation you can do smth like this:
public class MyRoleChecker implements RoleChecker {
public boolean isInRole(Principal principal, String role) {
return principal.getRole().equals(role);
}
}
Edited:
On the other hand as you use the new API, you need to implement SecurityContext and inject it using #Context in your resource methods.
Then you fetch roles list from the storage by username. The storage implementation is up to you. Please refer to this example
#Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Auth-Token");
String subject = validateToken(authHeaderVal); //execute custom authentication
if (subject!=null) {
final SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return subject;
}
};
}
#Override
public boolean isUserInRole(String role) {
List<Role> roles = findUserRoles(subject);
return roles.contains(role);
}
#Override
public boolean isSecure() {
return uriInfo.getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
return "Token-Based-Auth-Scheme";
}
});
}
}
}

Categories

Resources