SSL Endpoint with Heroku and 303 - rest

I have created an SSL Endpoint with heroku. I have a test environment and a live environment. I have a REST call that generates a 303. Since Heroku handles the SSL in it's router, I'm not sure how I can detect if my SEE OTHER URL should create an HTTP or HTTPS based URI. Here's some sample code:
#GET
#Path( "/job/{jobId}" )
public Response getCallStatus( #PathParam( "jobId" ) Long jobId, #Context UriInfo uriInfo ) throws Exception {
if ( !jobService.isDone( jobId ) )
return build( Response.ok( POLLING_FREQUENCY ) );
URI jobLocation = uriInfo.getAbsolutePathBuilder().path( "result" ).build();
return build( Response.seeOther( jobLocation ) );
}
Because my server isn't handling the SSL (heroku is) the absolute path for the REST call will use HTTP instead of HTTPS. If I hard code HTTPS I will break my unit tests or other environment that do not require the HTTPS protocol.
Any thoughts? Or am I misunderstanding how heroku is doing this?

Okay, so here's the answer. Heroku does NOT forward the request as HTTPS. Because of this you need to look into the x-fowarded-proto header to decide what the 303 location is that you should send back to your client. The above code sample would change to something like this:
#GET
#Path( "/job/{jobId}" )
public Response getCallStatus( #PathParam( "jobId" ) Long jobId, #Context UriInfo uriInfo, #Context HttpHeaders headers ) throws Exception {
if ( !jobService.isDone( jobId ) )
return build( Response.ok( POLLING_FREQUENCY ) );
UriBuilder builder = uriInfo.getAbsolutePathBuilder().path( "result" );
String scheme = headers.getHeaderString( "x-forwarded-proto" );
if ( scheme != null )
builder.scheme( scheme );
return build( Response.seeOther( builder.build() ) );
}
That's basically it.
But a better way to handle it that would not require ANY changes in the coded REST METHOD would be to add a container request filter like this:
#PreMatching
public class HerokuContainerRequestFilter implements ContainerRequestFilter {
#Override
public void filter( ContainerRequestContext ctx ) throws IOException {
List<String> schemes = ctx.getHeaders().get( "x-forwarded-proto" );
if ( schemes != null && !schemes.isEmpty() ) {
String scheme = schemes.get( 0 );
UriBuilder builder = ctx.getUriInfo().getRequestUriBuilder();
ctx.setRequestUri( builder.scheme( scheme ).build() );
}
}
}
Then you just register this filter with your RestConfig like this:
public class RestApplication extends ResourceConfig {
public RestApplication() {
packages( "com.myapp.rest.service" );
// along with any other providers, etc that you register
register( HerokuContainerRequestFilter.class );
}
}

While user1888440 answer is totally working I'd rather configure https forwarding at server level.
For example, if you are using an embedded jetty as you're heroku web server you could use jetty built-in org.eclipse.jetty.server.ForwardedRequestCustomizer :
Customize Requests for Proxy Forwarding.
This customizer looks at at HTTP request for headers that indicate it has been forwarded by one or more proxies. Specifically handled are:
X-Forwarded-Host
X-Forwarded-Server
X-Forwarded-For
X-Forwarded-Proto
If these headers are present, then the Request object is updated so that the proxy is not seen as the other end point of the connection on which the request came
So instead of starting your server with :
Server server = new Server(port);
You could use :
Server server = new Server();
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.addCustomizer(new ForwardedRequestCustomizer());
ServerConnector serverConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
serverConnector.setPort(port);
server.addConnector(serverConnector);

Related

Optional path parameter in vertx

I want to use a get request to get optional path parameters in a single end point . For example :-
if user hit below api :-
/metric/monitor/:id
or
/metric/:id
here id is path parameter than i need to route to the same function in a single end point using get request. How can i do the same ?
You can specify a route using regular expressions.
Assuming the id is a number:
router.getWithRegex( "\\/metric(\\/monitor)?\\/(?<ID>\\d+)" ).respond( this::monitor );
and then read the value:
long id = Long.parseLong( ctx.pathParam( "ID" ) );
This is how I usually declare the route using Vert.x Web 4.3.1:
public class MyVerticle extends AbstractVerticle {
#Override
public void start(Promise<Void> startPromise) {
Router router = Router.router( vertx );
BodyHandler bodyHandler = BodyHandler.create();
router.post().handler( bodyHandler );
router.getWithRegex( "\\/metric(\\/monitor)?\\/(?<ID>\\d+)" ).respond( this::monitor );
// Keeping track of it so that I can close it later
this.httpServer = vertx.createHttpServer();
final Future<HttpServer> startHttpServer = httpServer
.requestHandler( router )
.listen( HTTP_PORT )
.onSuccess( v -> LOG.infof( "✅ HTTP server listening on port %s", HTTP_PORT ) );
startHttpServer
.onSuccess( s -> startPromise.complete() )
.onFailure( startPromise::fail );
}
private Future<Result> monitor(RoutingContext ctx) {
long id = Long.parseLong( ctx.pathParam( "ID" ) );
Result result = ... // Do something
return Future.succeededFuture(result);
}
More details about routing with regular expressions are available in the documentation.
But, to be fair, creating two separate entry points seems easier for this case:
router.get( "/metric/monitor/:id" ).respond( this::monitor );
router.get( "/metric/:id" ).respond( this::monitor );

Can netty establish a connect connection with an https link?

I want to complete a function with netty
http send data to netty -> netty proxy data to target https
I start a client to connet target link.
I tried proxy to http ,it's ok
But when i use https link , future.isSuccess() return false
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
Bootstrap b = new Bootstrap();
b.group(ctx.channel().eventLoop())
.channel(ctx.channel().getClass())
.handler(new HttpProxyInitializer(ctx.channel()));
ChannelFuture f = b.connect("https://xxxx",443);
outboundChannel = f.channel();
f.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
future.channel().writeAndFlush(msg);
} else {
ctx.channel().close();
}
}
});
}
}
So is netty connet https not feasible?
How you connect does not look correct.
You would use:
b.connect("hostname", 443)
Ensure your HttpProxyInitializer also add the SslHandler to the ChannelPipeline.
Also if things not work you should inspect the future.cause() as it will contain a Throwable which will provide details about why the operation failed.

Integration Tests fail with JWT Authorization on OpenLiberty

Integration Tests (production code works well) fail while requesting REST endpoints secured with #RolesAllowed.
Following error is thrown:
[5/20/19 8:44:21:363 CEST] 00000109 com.ibm.ws.security.jaspi.JaspiServiceImpl I CWWKS1652A: Authentication failed with status AuthStatus.SEND_FAILUR for the web request
/banking/users/bed6109f-ef8a-47ec-8fa4-e57c71415a10. The user defined Java Authentication SPI for Containers (JASPIC) service null has determined that the authentication data is not valid.
Project is based on OpenLiberty with JWT. The difference is in the UI part. My UI is based on Angular, so for authentication (JWT issuing) following REST Endpoint is used:
#RequestScoped
#Path("/tokens")
#PermitAll
public class AuthResource {
#Inject
private SecurityContext securityContext;
#Inject
private AuthService authService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getJwt() {
if (securityContext.isCallerInRole("USER") || securityContext.isCallerInRole("ADMIN")) {
String name = securityContext.getCallerPrincipal().getName();
AuthPojo authPojo = authService.createJwt(name);
return Response.ok(authPojo).build();
}
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
So:
UI (Angular) calls https://localhost:5051/tokens with Header "Authorization: Basic ENCODED_USERNAME_PASSWORD"
Backend responds with newly generated JWT Token in body and Header "Set-Cookie: LtpaToken2=SOME_TOKEN; Path=/; HttpOnly"
UI uses this token for all other requests against REST Endpoints annotated with "#RolesAllowed({"ADMIN", "USER" })"
Once again, in production code, all this schema works well, but Integration Tests fail.
Here is Integration Test code:
public class MyResourceIT {
private static final String URL = "https://localhost:" +
System.getProperty("liberty.test.ssl.port") + "/users/" + USER_ID1;
private String authHeader;
#Before
public void setup() throws Exception {
authHeader = "Bearer " + new JwtVerifier().createAdminJwt(USER_NAME1);
}
#Test
public void getUserAndAccounts() {
Response response = HttpClientHelper.processRequest(URL, "GET", null, authHeader);
System.out.println("My URL: " + URL);
System.out.println("My Header: " + authHeader);
assertThat("HTTP GET failed", response.getStatus(), is(Response.Status.OK.getStatusCode()));
}
}
Looks like the problem why 401 instead 200 is returned is LtpaToken2 Cookie which is not set in Test. Instead Header "Authorization: Bearer JWT_TOKEN" is used, but this doesn't work.
I Expect that Endpoint secured with "#RolesAllowed" should respond with 200 when header "Authorization: Bearer JWT_TOKEN" is provided. Are there some tricks that should be done with a cookie?
UPDATE 2019-05-23
This is the whole project.
Example test is located here. The failing test is ignored
#Test
public void getUserAndAccounts_withJwt_authorized() throws IOException {
Response response = HttpClientHelper.processRequest(URL, "GET", null, authHeader, null);
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
}
JWT token is created within following class in the #Before annotated method:
private String authHeader;
#Before
public void setup() throws Exception {
authHeader = "Bearer " + new JwtVerifier().createAdminJwt(USER_NAME1);
}
One thing to notice, that project is based on the following project.
Since the CWWKS1652A message was issued without a provider name, this indicates that appSecurity-3.0 is set and that at least a JSR-375 (a.k.a. Java EE Security API Specification) HttpAuthenticationMechanism is configured for the application, either via annotation or bean implementation. This causes an internal JASPIC provider to be created, therefore the null in the CWWKS1652A message, and this provider invokes the configured HttpAuthenticationMechanism that returns a AuthStatus.SEND_FAILURE status.
Please ensure that you intend to use an HttpAuthenticationMechanism and that valid authentication credentials are passed when challenged by this mechanism.
If it is determined that there is no HttpAuthenticationMechanism configured, then determine if there is an external JASPIC provider factory (AuthConfigFactory implementation) set via the authconfigprovider.factory property. In either case, it is the provider that responds with the AuthStatus.SEND_FAILURE seen in the message.

GWT-APACHE CXF header

I have a CXF JAX-RS service and a GWT MVP4G presenter.
I call the service with the RequestBuilder and set Content-Type header to application/json.
But in the server side REST method do not call .
REST code is :
class PlayerService{
#POST
#Path("addplayer")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
String createOrSaveNewPLayerInfo(PlayerType playerType);
}
GWT code:
RequestBuilder rq = new RequestBuilder(RequestBuilder.POST, url)
rq.setHeader("Content-Type", "application/json");
rq.sendRequest(s, new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
LOGGER.info(">" + response.getStatusCode() + "<");
}
#Override
public void onError(Request request, Throwable exception) {
LOGGER.info(">>" + exception.getMessage() + "<<");
}
});
I assume, that your GWT application is running on the Jetty server and your service on a Tomcat server. In this case you have two different ports: 8080 & 8888. Calling the service on 8080 will be blocked by the Same Origin Policy.
To solve this, you can switch off the policy (look for CORS). Bad idea.
Instead run your GWT application inside a Tomcat. In this case you will not have any problems with the SOP.
To set up a external server with GWT take a look here.

Apache Wink Client - Test a REST service using form auth

I am trying to use the Wink RestClient to do functional testing on a Rest service endpoint. I use mocks for unit testing but I'd like to functionally test it as an endpoint consumer.
I understand some will object to me calling it a REST endpoint while using form-based auth but that is the current architecture I have.
The majority of the resources I want to test are protected resources and the application (running on Tomcat6) is protected by form authentication. (as in the below web.xml snippet).
What I've tried so far is to make an initial call to an unprotected resource, to obtain the set-cookie header, that contains JSESSIONID, and use that JSESSIONID in the header ( via Resource.cookie() ) in subsequent requests but that does not yield fruit.
web.xml
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/login.html?failure=true</form-error-page>
</form-login-config>
</login-config>
My Wink RestClient code looks like below. All responses are 200, but two things I notice are that the response from the call to /j_security_check/ does not include the jsessionid cookie, and the call to the protected resource said I had a signin failure. The payload for the call to j_security_check was captured directly from a previous successful browser request intercepted.
ClientConfig config = new ClientConfig();
config.setBypassHostnameVerification(true);
RestClient restClient = new RestClient(config);
Resource unprotectedResource = restClient.resource( BASE_URL + "/");
unprotectedResource.header( "Accept", "*/*" );
ClientResponse clientResponse = unprotectedResource.get();
String response = clientResponse.getEntity(String.class);
// get jSession ID
String jSessionId = clientResponse.getHeaders().get("set-cookie").get(0);
jSessionId = jSessionId.split(";")[0];
System.out.println(jSessionId);
// create a request to login via j_security_check
Resource loginResource = restClient.resource(BASE_URL + "/j_security_check/");
loginResource.accept("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
loginResource.header("referer", "http://localhost:8080/contextroot/");
loginResource.cookie( jSessionId );
loginResource.header("Connection", "keep-alive");
loginResource.header("Content-Type", "application/x-www-form-urlencoded");
loginResource.header("Content-Length", "41");
ClientResponse loginResponse = loginResource.post("j_username=*****&j_password=*************");
/* the loginResponse, as this point, does not have the jsessionid cookie, my browser client does */
Resource protectedResource = restClient.resource(BASE_URL + "/protected/test/");
systemResource.accept("application/json");
systemResource.cookie( jSessionId );
ClientResponse systemResponse = systemResource.get();
response = clientResponse.getEntity(String.class);
System.out.println(response);
Any thoughts or experience with using the Wink RestClient to exercise form-auth-protected resources would be greatly appreciated. I suppose I'd entertain other frameworks, I have heard of REST-Assured and others, but since the application uses Wink and the RestClient seems to provide me with what I need, I figured I'd stick with it.
Found the problem, and the solution
j_security_check was responding to my POST request (to authenticate), with a #302/redirect. That was being followed by the wink RestClient, but my JSESSIONID cookie was not being appended to it. That was causing the response (from the redirected URL) to contain a set-cookie header, with a new header. My subsequent calls, into which I inserted the JSESSIONID from the first call, failed, because that cookie was expired. All I needed to do was instruct the RestClient to NOT follow redirects. If the redirect were necessary, I would construct it on my own, containing the appropriate cookie.
Chromium and Firefox carry the cookie from the original request to the redirected request so it's all good.
Here is some code that worked for me, using JUnit4, RestClient from the Apache Wink project (and a Jackson ObjectMapper)
#Test
public void testGenerateZipEntryName() throws JsonGenerationException, JsonMappingException, IOException
{
final ObjectMapper mapper = new ObjectMapper();
final String BASE_URL = "http://localhost:8080/rest";
// Configure the Rest client
ClientConfig config = new ClientConfig();
config.proxyHost("localhost"); // helpful when sniffing traffic
config.proxyPort(50080); // helpful when sniffing traffic
config.followRedirects(false); // This is KEY for form auth
RestClient restClient = new RestClient(config);
// Get an unprotected resource -- to get a JSESSIONID
Resource resource = restClient.resource( BASE_URL + "/");
resource.header( "Accept", "*/*" );
ClientResponse response = resource.get();
// extract the jSession ID, in a brittle and ugly way
String jSessId = response.getHeaders().get("set-cookie").get(0).split(";")[0].split("=")[1];
// Get the login resource *j_security_check*
resource = restClient.resource(BASE_URL + "/j_security_check");
resource.cookie("j_username_tmp=admin; j_password_tmp=; JSESSIONID=" + jSessId);
resource.header("Content-Type", "application/x-www-form-urlencoded");
resource.header("Content-Length", "41");
// Verify that login resource redirects us
response = resource.post("j_username=admin&j_password=***********");
assertTrue( response.getStatusCode() == 302 );
// Grab a public resource
resource = restClient.resource(BASE_URL + "/");
resource.cookie("j_username_tmp=admin; j_password_tmp=; JSESSIONID=" + jSessId);
response = resource.get();
// verify status of response
assertTrue( response.getStatusCode() == 200 );
// Grab a protected resource
resource = restClient.resource(BASE_URL + "/rest/system");
resource.cookie("j_username_tmp=admin; j_password_tmp=; JSESSIONID=" + jSessId);
// Verify resource returned OK
response = resource.contentType("application/json").accept("*/*").get();
assertTrue( response.getStatusCode() == 200 );
// Deserialize body of protected response into domain object for further testing
MyObj myObj = mapper.readValue(response.getEntity(String.class), MyObj.class );
assertTrue( myObj.customerArchived() == false );
}