How to make JAX-WS webservice respond with specific http code - java

Just like the title says.
#WebService(
targetNamespace = "http://com.lalaland.TestWs",
portName = "TestWs",
serviceName = "TestWs")
public class TestWs implements TestWsInterface {
#EJB(name="validator")
private ValidatorLocal validator;
#WebMethod(operationName = "getStuff")
public List<StuffItem> getStuff(#WebParam(name = "aaa")String aaa,
#WebParam(name = "bbb")int bbb ) {
if ( ! validator.check1(...) )
return HTTP code 403 <------------ Here
if ( ! validator.check2(...) )
return HTTP code 404 <------------ Here
if ( ! validator.check3(...) )
return HTTP code 499 <------------ Here
return good list of Stuff Items
}
Is there anyway I can make a method return a specific HTTP code on demand? I know that some of the stuff, like authentication, internal server errors , etc make the the WS method return 500 and auth errors , but I would like to be able to send these in accordance with by business logic.
Anyone done this before? Been using jax-WS for some time and this was the first time I had this need, tried searching for it and couldn't find an answer anywhere.
Thanks

Only get the current instance of javax.servlet.http.HttpServletResponse and sends the error.
#WebService
public class Test {
private static final Logger LOG = Logger.getLogger(Test.class.getName());
#Resource
private WebServiceContext context;
#WebMethod(operationName = "testCode")
public String testCode(#WebParam(name = "code") int code) {
if (code < 200 || code > 299) {
try {
MessageContext ctx = context.getMessageContext();
HttpServletResponse response = (HttpServletResponse)
ctx.get(MessageContext.SERVLET_RESPONSE);
response.sendError(code, code + " You want it!");
} catch (IOException e) {
LOG.severe("Never happens, or yes?");
}
}
return code + " Everything is fine!";
}
}
See also List of HTTP status codes - Wikipedia, the free encyclopedia

Try this:
Create a SoapHandler like this: http://www.mkyong.com/webservices/jax-ws/jax-ws-soap-handler-in-server-side/ implementing the interface: Handler.handleResponse();
then, inside the handler you are avalaible to modify as you like the http headers, so you can add something like: http://download.java.net/jdk7/archive/b123/docs/api/javax/xml/ws/handler/MessageContext.html
Where you can use the: HTTP_RESPONSE_CODE as you want.
Other resource: http://docs.oracle.com/cd/E14571_01/web.1111/e13735/handlers.htm
Tip: think on soaphandlers as interceptors for soap messages

Related

Java Opensaml 3.4.6 : authnrequest subject is null - impossible to get user name

Developing a Java EE/JSF application, I am trying to include SAML sso functionality into it. Due to technical requirements (SAP BOBJ SDK) I need to use java 8, so I must stick with opensaml 3.x branch. As the application is some years old, I cannot add spring/spring-security to it just for SAML, that's why my code focuses on raw opensaml usage.
Mimicking the example code of this repository, I implemented the authentication basics:
This first code is called when I reach the "login" page. And send the AuthnRequest to my IDP
#Log4j2
#Named
public class SAMLAuthForWPBean implements Serializable {
private static final BasicParserPool PARSER_POOL = new BasicParserPool();
static {
PARSER_POOL.setMaxPoolSize(100);
PARSER_POOL.setCoalescing(true);
PARSER_POOL.setIgnoreComments(true);
PARSER_POOL.setIgnoreElementContentWhitespace(true);
PARSER_POOL.setNamespaceAware(true);
PARSER_POOL.setExpandEntityReferences(false);
PARSER_POOL.setXincludeAware(false);
final Map<String, Boolean> features = new HashMap<>();
features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
PARSER_POOL.setBuilderFeatures(features);
PARSER_POOL.setBuilderAttributes(new HashMap<>());
}
private String idpEndpoint = "url de azure por";
private String entityId = "glados";
private boolean isLogged;
#Inject
private LoginBean loginBean;
#Inject
private MainBean mainBean;
#Inject
private TechnicalConfigurationBean technicalConfigurationBean;
#PostConstruct
public void init() {
if (!PARSER_POOL.isInitialized()) {
try {
PARSER_POOL.initialize();
} catch (ComponentInitializationException e) {
LOGGER.error("Could not initialize parser pool", e);
}
}
XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(PARSER_POOL);
// forge auth endpoint
}
public boolean needLogon() {
return isLogged;
}
public void createRedirection(HttpServletRequest request, HttpServletResponse response)
throws MessageEncodingException,
ComponentInitializationException, ResolverException {
// see this link to build authnrequest with metadata https://blog.samlsecurity.com/2011/01/redirect-with-authnrequest-opensaml2.html
init();
AuthnRequest authnRequest;
authnRequest = OpenSAMLUtils.buildSAMLObject(AuthnRequest.class);
authnRequest.setIssueInstant(DateTime.now());
FilesystemMetadataResolver metadataResolver = new FilesystemMetadataResolver(new File("wp.metadata.xml"));
metadataResolver.setParserPool(PARSER_POOL);
metadataResolver.setRequireValidMetadata(true);
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
metadataResolver.initialize();
/*
* EntityDescriptor urlDescriptor = metadataResolver.resolveSingle( new CriteriaSet( new BindingCriterion(
* Arrays.asList("urn:oasis:names:tc:SAML:2.0:bindings:metadata"))));
*/
/*entityId = "https://192.168.50.102:8443/360.suite/loginSAML.xhtml";*/
entityId = "glados";
//idp endpoint, je pense => à obtenir des metadata
authnRequest.setDestination(idpEndpoint);
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
// app endpoint
authnRequest.setAssertionConsumerServiceURL("https://192.168.1.14:8443/360.suite/loginSAML.xhtml");
authnRequest.setID(OpenSAMLUtils.generateSecureRandomId());
authnRequest.setIssuer(buildIssuer());
authnRequest.setNameIDPolicy(buildNameIdPolicy());
MessageContext context = new MessageContext();
context.setMessage(authnRequest);
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.setProperty("resource.loader", "classpath");
velocityEngine.setProperty("classpath.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.init();
HTTPPostEncoder encoder = new HTTPPostEncoder();
encoder.setVelocityEngine(velocityEngine);
encoder.setMessageContext(context);
encoder.setHttpServletResponse(response);
encoder.initialize();
encoder.encode();
}
public String doSAMLLogon(HttpServletRequest request, HttpServletResponse response) {
isLogged = true;
technicalConfigurationBean.init();
return loginBean.generateSSOSession(request, technicalConfigurationBean.getSsoPreferences(),
new SamlSSO(technicalConfigurationBean.getCmsPreferences().getCms()));
}
private NameIDPolicy buildNameIdPolicy() {
NameIDPolicy nameIDPolicy = OpenSAMLUtils.buildSAMLObject(NameIDPolicy.class);
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat(NameIDType.TRANSIENT);
return nameIDPolicy;
}
private Endpoint URLToEndpoint(String URL) {
SingleSignOnService endpoint = OpenSAMLUtils.buildSAMLObject(SingleSignOnService.class);
endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
endpoint.setLocation(URL);
return endpoint;
}
private Issuer buildIssuer() {
Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
issuer.setValue(entityId);
return issuer;
}
}
The redirect is successfully processed and the IDP sends back a POST request to my application that call this code :
#Override
public IEnterpriseSession logon(HttpServletRequest request) throws SDKException, Three60Exception {
HTTPPostDecoder decoder = new HTTPPostDecoder();
decoder.setHttpServletRequest(request);
AuthnRequest authnRequest;
try {
decoder.initialize();
decoder.decode();
MessageContext messageContext = decoder.getMessageContext();
authnRequest = (AuthnRequest) messageContext.getMessage();
OpenSAMLUtils.logSAMLObject(authnRequest);
// Here I Need the user
String user = authnRequest.getSubject().getNameID().getValue();
// BOBJ SDK
String secret = TrustedSso.getSecret();
ISessionMgr sm = CrystalEnterprise.getSessionMgr();
final ITrustedPrincipal trustedPrincipal = sm.createTrustedPrincipal(user, cms, secret);
return sm.logon(trustedPrincipal);
} catch (ComponentInitializationException | MessageDecodingException e) {
return null;
}
}
The issue here is that getSubject() is null on this query.
What did I miss here? Do I need to perform other requests? Do I need to add other configuration in my AuthnRequest?
As stated in the comment, I found the reason why my code was not working.
As I also asked this question on a french forum, can can find the translation of this answer here.
Short answer :
Opensaml knows where to send the authn request thanks to the SAMLPeerEntityContext. In my code I put my own application as the target of this request instead of using the idp HTTP-POST bind endpoint. Once this was changed, everything worked, the idp was answering back the SAMLResponse with proper name.
Long version
On my code, I was building the entity context like this :
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
This code forces the authn request to be sent to my own application instead of the IDP. As this is the request, it cannot contain the identity.
If I replace this URL by idpEndpoint which I got from the IDP metadata file, the full workflow works as expected.
First something will not work as my IDP forces requests to be signed, so I need to add a signature part.
The "signing and verification" sample of this repository just works for that.
Then, as I need a real identity, I must NOT ask for a transient nameid. In my tests, UNSPECIFIED worked, but PERSISTENT should also make it.
Last, in the ACS receiver, I do NOT receive an authn request but a SAMLResponse with assertions. The code will therefore look like :
String userName =
((ResponseImpl) messageContext.getMessage()).getAssertions().get(0).getSubject().getNameID()
.getValue();
I simplified the code but one, of course, has to check that :
(((ResponseImpl)messageContext.getMessage()).getStatus() is SUCCESS
signatures are valid
assertions are properly populated
Thanks #identigral for your answer in the comment

Authorization in JAX-RS

I am developing an application using javaEE / Wildfly and JAX-RS for the restful service.
I have this kind of endpoint :
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response addSchool(SchoolDto schoolDto, #HeaderParam("token") String userToken) {
List<String> actionsNeeded = new ArrayList<String>(
Arrays.asList(
"create school"
));
if (authService.userHasActionList(userToken, actionsNeeded) == false )
{
return authService.returnResponse(401);
}
Response addSchoolServiceResponse = schoolResponse.create(schoolDto);
return addSchoolServiceResponse;
}
Using the token in Header my auth service will check if the user account has, in his list of authorized actions, those that are necessary to use the checkpoint.
It's working, but I'm repeating that on each checkpoint ... I'm looking for a way to do that :
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Annotation("action 1 needed", "Action 2 needed")
public Response addSchool(SchoolDto schoolDto, #HeaderParam("token") String userToken) {
Response addSchoolServiceResponse = schoolResponse.create(schoolDto);
return addSchoolServiceResponse;
}
an annotation where i can pass some parameters (my actions and most important be able to have the user token) who trigger using filter or whatever the security check return a 401 or let the method to be executed if user is allowed to be there.
I've find a lot of stuff (#Secured etc...) for security based on role but not on action like that
Is someone already did something like that ?
Finally I've started all over and it's working, my principal problem was to access token in the header and working with annotations and it's ok now (just need to insist and try one more time i assume ...) here is what it's look likes :
#Provider
#Actions
public class AuthorizationFilter implements ContainerRequestFilter {
#EJB
AuthService authService;
#Context
private ResourceInfo resourceInfo;
List<String> actionsNeeded = new ArrayList<String>();
#Override
public void filter(ContainerRequestContext reqContext) throws IOException {
Actions annotations = resourceInfo.getResourceMethod().getAnnotation(Actions.class);
String token;
try {
token = reqContext.getHeaders().get("token").get(0);
for (String annotation : annotations.value()) {
actionsNeeded.add(annotation);
}
if (authService.userHasActionList(token, actionsNeeded) == false )
{
reqContext.abortWith(authService.returnResponse(401));
return;
}
} catch (Exception e) {
System.out.println("Headers 'token' does not exist !");
reqContext.abortWith(authService.returnResponse(400));
}
}
}

Java: Get JSON from POST with HttpServletRequest?

I'm trying to get the body of a POST request by using HttpServletRequest or UriInfo. Given a class like this one (reduced for this question):
#Path("/nodes")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
public class Nodes {
public NodeResource() {
//initial stuff goes here
}
/**
* gives an empty response. For testing only!
*/
#POST
#Consumes("application/json")
#Path("{id}/test-db-requests")
public Response giveNodes(#PathParam("id") final String id, #Context HttpServletRequest request, #Context UriInfo uriInfo){
//String readReq = request.getQueryString(); //would work for GET
MultivaluedMap<String,String> readParams = uriInfo.getQueryParameters();
LOG.debug("what is readParams?", readParams); //goes, but shows nothing
if (readParams != null) {
LOG.debug("null or not?"); //goes, too
for (Map.Entry<String,List<String>> entry: readParams.entrySet()) {
List<String> values = entry.getValue();
LOG.debug("params POST key: {}", entry.getKey()); // goes not
for (String val: values) {
LOG.debug("params POST values: {}", val);
}
LOG.debug("params POST next entry:::");
}
}
List<?> results = null; //currentDBRequest(id);
List<?> content = new ArrayList<>();
if (results != null) {
content = results;
}
return Response.ok(content).build();
}
}
Instead of using
MultivaluedMap<String,String> readParams = uriInfo.getQueryParameters();
//not possible at all - for GET only!? See first comment.
I also tried to use
Map<String,String[]> readParams = request.getParameterMap();
//what is about this one?
with different following code of course. But that did not work, either.
So when I fire a simple request like /nodes/546c9abc975a54c398167306/test-db-requests with the following body
{
"hi":"hello",
"green":"tree"
}
(using an JSON Array does not change anything)
and stuff in the HEADER (some informations):
Content-Type: application/json; charset=UTF-8
Accept: application/json, text/plain, */*
Connection: keep-alive
the result is disappointing, readParams is not null, but does not contain any data. Before I start to play with getReader I wanted to ask: what am I doing wrong? Is there a problem in my POST, in my Java code or in the used HttpServletRequest method(s)? Thanks!
Related questions (where I found some possible solutions), among others:
How can I grab all query parameters in Jersey JaxRS?
How to access parameters in a RESTful POST method
Alright, Jackson would actually do this for me. Just use the argument of the method, which you want to use. (See examples below.)
But you would probably not use a POST in combination with an id parameter. POST is usually used for saving fresh resources, which do not have an id (in the DB, a primary key). Moreover the path /api/{resource_name}/{id}/{some_view} would be useful for GET. Just api/{resource_name}/{id} for a GET (single entry) or a PUT (update an existing entry).
Assume you are in a resource for Pet.class. You want to catch the POSTs for this class in order to do something special with them, based on the view test-db-requests. Then do:
#POST
#Consumes("application/json")
#Path("{id}/test-db-requests")
public Response giveNodes(final String pet, #PathParam("id") final String id){
//do stuff for POST with a strigified JSON here
}
or
#POST
#Path("{id}/test-db-requests")
public Response giveNodes(final Pet pet, #PathParam("id") final String id){
//do stuff for POST with an instance of pet here (useful for non
//polymorphic resources
}

Can I wrap all JAX-RS requests with custom pre-dispatch, post-dispatch and error-handler code?

I have a number of classes exposed as JAX-RS request "handlers", using javax.ws.rs.Path annotations. I want to add certain actions before every request and after each request. Also, I need to create a global application-wide exception handler, which will catch everything thrown by these handlers and protocol.
Is it possible to achieve this with standard JAX-RS without creating of a custom class inherited from com.sun.jersey.spi.container.servlet.ServletContainer (I'm using Jersey).
You can also use ExceptionMappers. This mechanism which catch the exception thrown by your service and convert it to the appropriate Response:
#Provider
public class PersistenceMapper implements ExceptionMapper<PersistenceException> {
#Override
public Response toResponse(PersistenceException arg0) {
if(arg0.getCause() instanceof InvalidDataException) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
...
}
}
}
For more information see:
JAX-RS using exception mappers
You could create a proxy RESTful service and use this as the entry point to all your other RESTful services. This proxy can receive requests, do any pre-processing, call the RESTful service required, process the response and then return something to the caller.
I have a set up like this in a project I've been working on. The proxy performs functions like authentication, authorisation and audit logging. I can go into further details if you like.
Edit:
Here is an idea of how you might want to implement a proxy that supports GET requests;
#Path("/proxy")
public class Proxy
{
private Logger log = Logger.getLogger(Proxy.class);
#Context private UriInfo uriInfo;
#GET
#Path("/{webService}/{method}")
public Response doProxy(#Context HttpServletRequest req,
#PathParam("webService") String webService,
#PathParam("method") String method)
{
log.debug("log request details");
//implement this method to work out the URL of your end service
String url = constructURL(req, uriInfo, webService, method);
//Do any actions here before calling the end service
Client client = Client.create();
WebResource resource = client.resource(url);
try
{
ClientResponse response = resource.get(ClientResponse.class);
int status = response.getStatus();
String responseData = response.getEntity(String.class);
log.debug("log response details");
//Do any actions here after getting the response from the end service,
//but before you send the response back to the caller.
return Response.status(status).entity(responseData).build();
}
catch (Throwable t)
{
//Global exception handler here
//remember to return a Response of some kind.
}
}
You can use filters to read and modify all requests and responses.

Running JUnit Tests on a Restlet Router

Using Restlet I have created a router for my Java application.
From using curl, I know that each of the different GET, POST & DELETE requests work for each of the URIs and return the correct JSON response.
I'm wanting to set-up JUnit tests for each of the URI's to make the testing process easier. However, I'm not to sure the best way to make the request to each of the URIs in order to get the JSON response which I can then compare to make sure the results are as expected. Any thoughts on how to do this?
You could just use a Restlet Client to make requests, then check each response and its representation.
For example:
Client client = new Client(Protocol.HTTP);
Request request = new Request(Method.GET, resourceRef);
Response response = client.handle(request);
assert response.getStatus().getCode() == 200;
assert response.isEntityAvailable();
assert response.getEntity().getMediaType().equals(MediaType.TEXT_HTML);
// Representation.getText() empties the InputStream, so we need to store the text in a variable
String text = response.getEntity().getText();
assert text.contains("search string");
assert text.contains("another search string");
I'm actually not that familiar with JUnit, assert, or unit testing in general, so I apologize if there's something off with my example. Hopefully it still illustrates a possible approach to testing.
Good luck!
Unit testing a ServerResource
// Code under test
public class MyServerResource extends ServerResource {
#Get
public String getResource() {
// ......
}
}
// Test code
#Autowired
private SpringBeanRouter router;
#Autowired
private MyServerResource myServerResource;
String resourceUri = "/project/1234";
Request request = new Request(Method.GET, resourceUri);
Response response = new Response(request);
router.handle(request, response);
assertEquals(200, response.getStatus().getCode());
assertTrue(response.isEntityAvailable());
assertEquals(MediaType.TEXT_PLAIN, response.getEntity().getMediaType());
String responseString = response.getEntityAsText();
assertNotNull(responseString);
where the router and the resource are #Autowired in my test class. The relevant declarations in the Spring application context looks like
<bean name="router" class="org.restlet.ext.spring.SpringBeanRouter" />
<bean id="myApplication" class="com.example.MyApplication">
<property name="root" ref="router" />
</bean>
<bean name="/project/{project_id}"
class="com.example.MyServerResource" scope="prototype" autowire="byName" />
And the myApplication is similar to
public class MyApplication extends Application {
}
I got the answer for challenge response settings in REST junit test case
#Test
public void test() {
String url ="http://localhost:8190/project/user/status";
Client client = new Client(Protocol.HTTP);
ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC,"user", "f399b0a660f684b2c5a6b4c054f22d89");
Request request = new Request(Method.GET, url);
request.setChallengeResponse(challengeResponse);
Response response = client.handle(request);
System.out.println("request"+response.getStatus().getCode());
System.out.println("request test::"+response.getEntityAsText());
}
Based on the answer of Avi Flax i rewrite this code to java and run it with junitparams, a library that allows pass parametrized tests. The code looks like:
#RunWith(JUnitParamsRunner.class)
public class RestServicesAreUpTest {
#Test
#Parameters({
"http://url:port/path/api/rest/1, 200, true",
"http://url:port/path/api/rest/2, 200, true", })
public void restServicesAreUp(String uri, int responseCode,
boolean responseAvaliable) {
Client client = new Client(Protocol.HTTP);
Request request = new Request(Method.GET, uri);
Response response = client.handle(request);
assertEquals(responseCode, response.getStatus().getCode());
assertEquals(responseAvaliable, response.isEntityAvailable());
assertEquals(MediaType.APPLICATION_JSON, response.getEntity()
.getMediaType());
}
}

Categories