I am using jaxws-rt and have WSDLs built and the web services all created. Everything works fine but I am wonder if there is a way to see if more parameters were tagged onto the URL from the web service.
As a web service provider you can access the query string used by the SOAP client via the HttpServletRequest in the MessageContext:
package org.example.sampleservice;
import javax.annotation.Resource;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.servlet.http.HttpServletRequest;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
#WebService(endpointInterface = "org.example.sampleservice.SampleService")
public class SampleServiceImpl implements SampleService {
#Resource
private WebServiceContext ctx;
#Override
public String sayHello(String name) {
HttpServletRequest request = (HttpServletRequest) ctx.getMessageContext().get(MessageContext.SERVLET_REQUEST);
String result = String.format("Hello, %s (invoked with endpoint query parameters %s)", name,
request.getQueryString() == null ? "[no endpoint URL query parameters found]"
: request.getQueryString());
return result;
}
}
You can either get the query string as one string as I have above (request.getQueryString()) or via other standard HttpServletRequest methods:
getParameterMap()
getParameterNames()
getParameterValues()
getRequestURL()
Example soap client for this class:
package org.example.consumer;
import java.net.URL;
import javax.xml.ws.BindingProvider;
import org.example.sampleservice.SampleService;
import org.example.sampleservice.SampleServiceImplService;
public class SayHelloClientApp {
public static void main(String[] args) throws Exception {
URL wsdlLoc = new URL("http://localhost:8081/samplews/sample?WSDL");
SampleServiceImplService svc = new SampleServiceImplService(wsdlLoc);
SampleService port = svc.getSampleServiceImplPort();
BindingProvider bp = (BindingProvider) port;
String endpointURL = "http://localhost:8081/samplews/sample?a=1&b=2&c=3";
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointURL);
String result = port.sayHello("java");
System.out.println(String.format("Result:\n%s", result));
}
}
Prints
Result:
Hello, java (invoked with endpoint query parameters a=1&b=2&c=3)
Related
I have a self contained Jersey test using JerseyExtension (JerseyExtension) with JUnit5 (since JerseyTest does not work with JUnit5 unless you use the vintage engine) and subsequent calls to the container are getting different session. Is there a way to keep the session store same between the calls?
package com.test.jerseysession;
import com.github.hanleyt.JerseyExtension;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JerseyTestWithGrizzly {
private final static TestContainerFactory testContainerFactory;
private final ServletContainer servletContainer;
private final ResourceConfig resourceConfig;
private final DeploymentContext deploymentContext;
static {
testContainerFactory = new GrizzlyWebTestContainerFactory();
}
#RegisterExtension
#SuppressWarnings("unused")
JerseyExtension jerseyExtension = new JerseyExtension(
this::getTestContainerFactory,
this::configureDeploymentContext,
this::configureJerseyClient);
public JerseyTestWithGrizzly() {
this.resourceConfig = new ResourceConfig()
.packages("com.test.jerseysession")
.register(getClass());
this.servletContainer = new ServletContainer(resourceConfig);
this.deploymentContext = ServletDeploymentContext.builder(resourceConfig)
.servlet(servletContainer)
.servletPath("api")
.build();
}
#Path("session")
public static class SessionResource {
#GET
public String get(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
Object obj = session.getAttribute("name");
return session.getId() + ": " + obj;
}
#PUT
public String put(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
session.setAttribute("name", "foo");
return session.getId()+": Set name attribute called";
}
}
protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
assertNotNull(extensionContext);
assertNotNull(clientConfig);
return clientConfig;
}
protected DeploymentContext configureDeploymentContext(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return deploymentContext;
}
protected TestContainerFactory getTestContainerFactory(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return testContainerFactory;
}
#Test
public void testSessionSet(WebTarget target) {
// Call PUT which sets attribute called 'name'
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
// Call GET which should be able to find 'name' in session set by previous call
Response response1 = target.path("session").request().get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}
}
Sample output:
PUT: status=200 response=8373522406385125383: Set name attribute called
GET: status=200 response=8264425692811867393: null
The session ID changed between the call to PUT and GET.
The client used by Jersey test framework, does not behave like a browser when it comes to Set-Cookie/Cookie headers. The two requests are not connected and JSESSIONID set by first response is not propagated to next request. While the framework is aware of the JSESSIONID if present, it does not span requests and needs to be manually copied forward.
Changing the test method to following works:
#Test
public void testSessionSet(WebTarget target) {
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
Invocation.Builder nextRequestBuilder = target.path("session").request();
NewCookie jsessionid = response0.getCookies().get("JSESSIONID");
if (jsessionid != null) {
nextRequestBuilder.cookie(jsessionid);
}
Response response1 = nextRequestBuilder.get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}
I have a java bean which has both multipart and String data. I am trying to pass it in a rest client call which takes this java bean input and processes it.
Below are my model class, controller and rest client.
On making a call from my rest client , I am getting this exception.
Exception in thread "main" org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.techidiocy.models.NHPdfMergeRequest] and content type [multipart/form-data]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:810)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:594)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:384)
Model Class
import org.springframework.web.multipart.MultipartFile;
public class Candidate {
private String firstName;
private String lastName;
private MultipartFile resume;
//getters and setters
}
Controller Class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
#RestController
public class CandidateController {
#Autowired
private CandidateService candidateService;
#RequestMapping(method=RequestMethod.POST, path="/add")
public void add(#RequestBody Candidate request) {
// do some processing
String firstName = request.getFirstName();
String lastName = request.getLastName();
MultipartFile resume = request.getResume();
candidateService.add(firstName, lastName, resume);
}
}
Rest Client
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.client.RestTemplate;
public class CandidateClient {
public static void main(String[] args) throws IOException {
String serverURL = "http://localhost:8080/add";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
Candidate candidate = new Candidate();
candidate.setFirstName("John");
candidate.setLastName("Doe");
candidate.setResume(new MockMultipartFile("tmp.pdf", FileUtils.readFileToByteArray(new File("/home/john/resume/john.pdf"))));
HttpEntity<Candidate> httpEntity = new HttpEntity<Candidate>(candidate, headers);
RestTemplate client = new RestTemplate();
client.postForEntity(serverURL, httpEntity, Resource.class);
}
}
Note: I had also tried to set the header content type as json in rest client and then I am getting all the values as Null in the controller. headers.setContentType(MediaType.APPLICATION_JSON);
I had also searched over the internet for this kind of scenario but I am unable to find a solution for this.
I had also tried to pass all the parameters separately (not as part of java bean) then I am able to make it work.
I have a RequestInterceptor class which is registered in InterceptorRegistry.
For every request to the application, I have used the preHandle() of HandlerInterceptorAdapter Class in RequestInterceptor Class, to Log all the request in Database.
My requirement is to avoid some of the URI coming to RequestInterceptor, in short, they are mandatory for application but I don't want to store that URI in Database.
Can you suggest what are the possible approach I can use to filter out the requests?
I thought using property file but this will create overhead in each request to preHandle().
Configuration File :
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
#Autowired
RequestInterceptor requestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestInterceptor);
}
}
RequestInterceptor Class: I am able to get URI using action variable
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
#Slf4j
#Component
public class RequestInterceptor extends HandlerInterceptorAdapter {
#Autowired
private UserAccessLogsService userAccessLogsService;
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object object) {
try {
String fullUrl;
/*returns the part of the full URL before query string separator character '?' */
StringBuilder requestURL;
requestURL = new StringBuilder(request.getRequestURL().toString());
/*returns the part of the full URL after query string separator character '?' */
String queryString = request.getQueryString();
/*returns URI */
String action = request.getRequestURI();
/*returns Remote Address */
String remoteAddress = request.getRemoteAddr();
if (queryString == null) {
fullUrl = requestURL.toString();
} else {
fullUrl = requestURL.append('?').append(queryString).toString();
}
/*returns loged in user */
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String userName = auth.getName();
UserAccessLogs userAccessLogs = new UserAccessLogs(LocalDateTime.now(), userName, remoteAddress, action, fullUrl);
userAccessLogsService.save(userAccessLogs);
} catch (Exception e) {
LOG.error("Error occurred while adding access logs for user ", e);
}
return true;
}
}
For eg: Below are the URI which is coming to application and stored in DB,
/ui/report/user_access_logs
/ui/report/user_activity
/api/systemuser/search
Now out of above URI I want this /api/systemuser/search not to store in DB
We were testing a REST webservice developed in jersey through postman rest client. It is a POST method and is annotated with #RolesAllowed. The full annotation the method is as follows:
#POST
#Path("/configuration")
#RolesAllowed("admin")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
When I requested this http://baseurl/configuration with the expected HTTP body content, I got 403 response(it is expected since it is allowed only for admin as it seems).
My doubt is how to access this service with the specified role via rest client.
So it seems like you set up the RolesAllowedDynamicFeature, but you have no authentication happening to set up the user and roles. What the RolesAllowedDynamicFeature does is lookup the SecurityContext, and calls the SecurityContext.isUserInRole(<"admin">) to see if the user in the SecurityContext has the role.
I imagine you don't know how the SecurityContext is set. There are a couple of ways. The first is through the servlet authentication mechanism. You can see more at Securing Web Applications from the Java EE tutorial.
Basically you need to set up a security realm or security domain on the server. Every server has it's own specific way of setting it up. You can see an example here or how it would be done with Tomcat.
Basically the realm/domain contains the users allowed to access the web app. Those users have associated roles. When the servlet container does the authentication, whether it be Basic authentication or Form authentication, it looks up the user from the credentials, and if the user is authenticated, the user and its roles are associated with the request. Jersey gathers this information and puts it into the SecurityContext for the request.
If this seems a bit complicated, an easier way to just forget the servlet container authentication and just create a Jersey filter, where you set the SecurityContext yourself. You can see an example here. You can use whatever authentication scheme you want. The important part is setting the SecurityContext with the user information, wherever you get it from, maybe a service that accesses a data store.
See Also:
securing rest services in Jersey
UPDATE
Here is a complete example of the second option using the filter. The test is run by Jersey Test Framework. You can run the test as is
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.DatatypeConverter;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.internal.util.Base64;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.glassfish.jersey.test.JerseyTest;
import static junit.framework.Assert.*;
import org.junit.Test;
public class BasicAuthenticationTest extends JerseyTest {
#Provider
#Priority(Priorities.AUTHENTICATION)
public static class BasicAuthFilter implements ContainerRequestFilter {
private static final Logger LOGGER = Logger.getLogger(BasicAuthFilter.class.getName());
#Inject
private UserStore userStore;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authentication = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authentication == null) {
throw new AuthenticationException("Authentication credentials are required");
}
if (!authentication.startsWith("Basic ")) {
return;
}
authentication = authentication.substring("Basic ".length());
String[] values = new String(DatatypeConverter.parseBase64Binary(authentication),
Charset.forName("ASCII")).split(":");
if (values.length < 2) {
throw new WebApplicationException(400);
}
String username = values[0];
String password = values[1];
LOGGER.log(Level.INFO, "{0} - {1}", new Object[]{username, password});
User user = userStore.getUser(username);
if (user == null) {
throw new AuthenticationException("Authentication credentials are required");
}
if (!user.password.equals(password)) {
throw new AuthenticationException("Authentication credentials are required");
}
requestContext.setSecurityContext(new MySecurityContext(user));
}
}
static class MySecurityContext implements SecurityContext {
private final User user;
public MySecurityContext(User user) {
this.user = user;
}
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return user.username;
}
};
}
#Override
public boolean isUserInRole(String role) {
return role.equals(user.role);
}
#Override
public boolean isSecure() { return true; }
#Override
public String getAuthenticationScheme() {
return "Basic";
}
}
static class AuthenticationException extends WebApplicationException {
public AuthenticationException(String message) {
super(Response
.status(Status.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"" + "Dummy Realm" + "\"")
.type("text/plain")
.entity(message)
.build());
}
}
class User {
public final String username;
public final String role;
public final String password;
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
}
class UserStore {
public final Map<String, User> users = new ConcurrentHashMap<>();
public UserStore() {
users.put("peeskillet", new User("peeskillet", "secret", "USER"));
users.put("stackoverflow", new User("stackoverflow", "superSecret", "ADMIN"));
}
public User getUser(String username) {
return users.get(username);
}
}
private static final String USER_RESPONSE = "Secured User Stuff";
private static final String ADMIN_RESPONSE = "Secured Admin Stuff";
private static final String USER_ADMIN_STUFF = "Secured User Admin Stuff";
#Path("secured")
public static class SecuredResource {
#GET
#Path("userSecured")
#RolesAllowed("USER")
public String getUser() {
return USER_RESPONSE;
}
#GET
#Path("adminSecured")
#RolesAllowed("ADMIN")
public String getAdmin() {
return ADMIN_RESPONSE;
}
#GET
#Path("userAdminSecured")
#RolesAllowed({"USER", "ADMIN"})
public String getUserAdmin() {
return USER_ADMIN_STUFF;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(SecuredResource.class)
.register(BasicAuthFilter.class)
.register(RolesAllowedDynamicFeature.class)
.register(new AbstractBinder(){
#Override
protected void configure() {
bind(new UserStore()).to(UserStore.class);
}
});
}
static String getBasicAuthHeader(String username, String password) {
return "Basic " + Base64.encodeAsString(username + ":" + password);
}
#Test
public void should_return_403_with_unauthorized_user() {
Response response = target("secured/userSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("stackoverflow", "superSecret"))
.get();
assertEquals(403, response.getStatus());
}
#Test
public void should_return_200_response_with_authorized_user() {
Response response = target("secured/userSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("peeskillet", "secret"))
.get();
assertEquals(200, response.getStatus());
assertEquals(USER_RESPONSE, response.readEntity(String.class));
}
#Test
public void should_return_403_with_unauthorized_admin() {
Response response = target("secured/adminSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("peeskillet", "secret"))
.get();
assertEquals(403, response.getStatus());
}
#Test
public void should_return_200_response_with_authorized_admin() {
Response response = target("secured/adminSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("stackoverflow", "superSecret"))
.get();
assertEquals(200, response.getStatus());
assertEquals(ADMIN_RESPONSE, response.readEntity(String.class));
}
}
Here is the only dependency needed to run the test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
Can I run a simple webservice like this:
#Path("/rs/hello")
public class HelloWorldProgram {
//path is default
#GET
#Produces(MediaType.TEXT_HTML)
public String sayHello() {
return "Hello, World!";
}
#GET
#Produces(MediaType.TEXT_XML)
#Path("/xml")
public String sayXMLHello() {
return "<?xml version=\"1.0\"?>" + "<hello> Hello" + "</hello>";
}
}
on the bundled with the JDK simple web server com.sun.net.httpserver.HttpServer ?
Yes, you can. Take a look at the "helloworld-pure-jax-rs" example of Jersey: https://github.com/jersey/jersey/tree/master/examples/helloworld-pure-jax-rs
No, you can't use com.sun.net.httpserver.HttpServer. You need a server compliant with Servlet API. Instead you can use for example org.glassfish.grizzly.http.server.HttpServer:
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
public class Server {
public static void main(String[] args) throws InterruptedException {
URI uri = UriBuilder.fromUri("http://localhost/").port(8888).build();
ResourceConfig rc = new ResourceConfig(HelloWorldProgram.class);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, rc);
Thread.currentThread().join(); // keep running
}
}
You will need a dependency to Jersey Container Grizzly2 Servlet.