Vertx - missing GET parameters while securing route using OAuth2 - java

I followed this example of securing route using OAuth2 with GitHub provider: http://vertx.io/docs/vertx-web/java/#_oauth2authhandler_handler and it works fine, except missing GET parameters after request redirection.
My code:
public class MyVerticle extends AbstractVerticle {
#Override
public void start() throws Exception {
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
OAuth2Auth authProviderGitHub = GithubAuth.create(vertx, "<CLIENT_ID>", "<CLIENT_SECRET>");
OAuth2AuthHandler oauth2 = OAuth2AuthHandler.create(authProviderGitHub, "http://localhost:8080/callback");
oauth2.setupCallback(router.route());
router.route("/protected/*").handler(oauth2);
Handler<RoutingContext> requestHandler = (routingContext) -> {
String paramValue = routingContext.request().getParam("param");
routingContext.response().end("PARAM: " + paramValue);
};
router.get("/endpoint").handler(requestHandler);
router.get("/protected/endpoint").handler(requestHandler);
server.requestHandler(router::accept).listen(8080);
}
}
I have two simple endpoints:
/endpoint // public, without protection
and
/protected/endpoint // protected with OAuth2
When I call from a browser /endpoint with
http://localhost:8080/endpoint?param=foo
it works as expected and return PARAM: foo, whereas when I call protected endpoint with
http://localhost:8080/protected/endpoint?param=foo
it correctly redirect me to GitHub login page, then return query to my handler but without GET parameters, so response from the endpoint is PARAM: null.
Any idea what I'm doing wrong?

On vert.x <= 3.4.2 only the path was being used for the redirect, the 3.5 series has been improved and can rely on the full uri, so your code will work on that version.

Related

Feign Registration - Spring Cloud - Change Target without ribbon over-ride

Introduction
I would like to be able to have two different spring profiles, and depending on the profile to change to a hardcoded address for our feign builders.
Currently was have the following:
return builder.target(cls, "http://" + serviceName);
But I would actually like to do the following and over-ride the address:
return builder.target(cls, "http://our-server:8009/" + serviceName);
Why
Sometimes we don't want to run all the services within our development environment. Additionally, some of the services are only available through a zuul gateway sometimes.
So we run the same code in different situations and conditions.
Technical Details
We have the following code that we use for building our Feign Clients.
We had been using the #FeignClient annotation in the past, but lately we decided to start building our feignClients manually.
Example below:
#FeignClient(name = "ab-document-store", configuration = MultiPartSupportConfiguration.class, fallback = DocumentStoreFallback.class)
We call the feignRegistrar class with the following command:
return registerFeignClient(DocumentStoreClient.class, true);
#RequiredArgsConstructor
//#Component
#Slf4j
public class FeignRegistrar {
#Autowired
private Decoder decoder;
#Autowired
private Encoder encoder;
#Autowired
private Client client;
#Autowired
private Contract feignContract;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Autowired
private List<RequestInterceptor> interceptors;
public <T> T register(Class<T> cls, String serviceName, boolean isDocumentStore) {
if(isDocumentStore){
encoder = new MultipartFormEncoder(new SpringEncoder(messageConverters));
}
//Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
for(RequestInterceptor interceptor : interceptors) {
builder.requestInterceptor(interceptor);
}
log.debug("Registering {} - as feign proxy ", serviceName);
return builder.target(cls, "http://" + serviceName);
}
public static class Slf4Logger extends Logger {
#Override
protected void log(String configKey, String format, Object... args) {
log.info("{} - {}", configKey, args);
}
}
}
Spring Cloud Property Over-ride
We have also been using property files such as application-ENV.property with entries such as the following:
ab-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
ab-document-store.ribbon.listOfServers: localhost:8025
Unfortunately, listOfServers is not enough for us. We would like to be able to assign a directory/path as well. Something like:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
Otherworkaround
I have thought about sneaking in a header into all requests such as X-SERVICE-NAME using a feign interceptor. Then we could point all services to an address (e.g. localhost:9001) , and forward/proxy those requests to localhost:9001/X-SERVICE-NAME.
However, I would prefer a much easier solution such as:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
But this doesn't work :(
Introduction
I found a solution for this using a proxy that detects a header.
So, I have a feign interceptor on the java-side that attaches a header x-service-name to every feign-request.
I also have a NodeJS proxy, that analyzes requests, finds x-service-name, and re-writes the requests to become: x-service-name/originalRequestPath.
This allows me to have all the microservices behind a zuul gateway but also access them using a eureka-over-ride.
Java-Feign-Interceptor
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(usedEncoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
NodeJS proxy
In the example, my zuul gateway ( or another proxy ) is on localhost:9001.
I'm listening on localhost:1200 .
let enableProxyForJava = process.env.ENABLE_PROXY_FOR_JAVA;
if (enableProxyForJava != undefined && enableProxyForJava.toLowerCase() === 'true') {
var httpProxyJava = require('http-proxy');
var proxyJava = httpProxyJava.createProxy();
gutil.log( gutil.colors.green('Enabling Proxy for Java. Set your Eureka overrides to localhost:1200.') );
require('http').createServer(function(req, res) {
console.log("req.headers['x-service-name'] = " + req.headers['x-service-name']);
console.log("Before req.url:"+ req.url);
if( req.headers['x-service-name'] != undefined){
let change = req.headers['x-service-name'] +req.url;
console.log("After req.url:"+ change);
req.url = change;
}
proxyJava.web(req, res, {
target: 'http://localhost:9001/'
});
}).listen(1200);
}
Property file inside Java Application that has feign clients
mbak-microservice1.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice1.ribbon.listOfServers: localhost:1200
mbak-microservice2.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice2.ribbon.listOfServers: localhost:1200
mbak-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-document-store.ribbon.listOfServers: localhost:1200

Passing WebSocket parameters to a programmatical endpoint

My front-end code looks like this:
const ws = new WebSocket("wss://localhost/yeah?param1=value1&param2=value2");
My endpoint at the backend is programmatical:
class YeahEndpoint extends Endpoint {
...
#Override
public void onOpen(Session session, EndpointConfig ec) {
Map<String, String> params = session.getPathParameters(); <-- returns empty map! No param1 or param2.
}
...
}
As I was not able to find relevant information on the web, I need to ask this here: how a programmatical endpoint obtains the request parameters?
I simply should have used session.getQueryString() instead of session.getPathParameters().
If you are using the Jetty as the back-end, look at the following code:
session.getUpgradeRequest().getParameter("param")

Spring - Retry request if service returns 409 HTTP Code

I have an Spring + CXF application which consumes a Transmission API: Transmission RPC running in another server.
According to Transmission docs, you need to send a token which is generated on the first request. The server then responds with a 409 http code along with a header containing the token. This token should be sent on all subsequent calls:
2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent
CSRF attacks. When your request has the wrong id -- such as when you
send your first request, or when the server expires the CSRF token --
the Transmission RPC server will return an HTTP 409 error with the
right X-Transmission-Session-Id in its own headers. So, the correct
way to handle a 409 response is to update your
X-Transmission-Session-Id and to resend the previous request.
I was looking for solution either using a CXF filter or interceptor, that basically will handle the 409 response and retry the initial request adding the token header. I'm thinking that clients can persist this token and send it in future calls.
I'm not very familiar with cxf so I was wondering if this can be accomplish and how. Any hint would be helpful.
Thanks!
Here spring-retry can be utilized which is now an independent project and no longer part of spring-batch.
As explained here retry callback will help make another call updated with the token header.
Pseudo code / logic in this case would look something like below
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
Make sure to configure the RetryTemplate with reasonable retry & backoff policies so as to avoid any resource contention / surprises.
Let know in comments in case of any queries / roadblock.
N.B.: RetryContext's implementation RetryContextSupport has the hasAttribute & setAttribute method inherited from Spring core AttributeAccessor
Assuming you are using Apache CXF JAX RS Client it is easy to do by just creating a custom Runtime Exception and ResponseExceptionMapper for it. So the idea is to manually convert 409 outcomes to some exception and then handle them correctly (in your case retry the service call).
See following code snipped for fully working example.
#SpringBootApplication
#EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
#Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
#Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
#Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
#Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
#Path("/post")
public interface TransmissionService {
#GET
#Path("/{a}")
#Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(#PathParam("a") Integer a, #HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}

Restlet URI Pattern

I work on 2.1-M7 version of Restlet (I have to update it but this is an another problem)
I use directly Restlet, without any webserver before it Starting a component. Adding some virtualhosts on it.
And in the host I add entrypoints with method attach(string uriPattern, Restlet entrypoint)
My problem is :
When I add with attach the uri "/test" with the entrypoint Test.class (with a method who print : "hello world") with a curl I can call "/testmeagain" and it's work (return "hello world") because it's a pattern?
So I use this : http://docs.oracle.com/javase/1.5.0/docs/api/java/util/regex/Pattern.html?is-external=true
And try "/test$" but in curl "/test" and "/testmeagain" return 404 now
Maybe I miss something?
Thank you if you have any suggestion or response to help me.
In fact, in Restlet, there is a matching mode for routes. Here is the behavior in the framework:
When you attach a route on a virtual, the default mode is "STARTS WITH". So with something like attach("/test", ...), URLs like /test and /testsomething will match.
When you attach a route on a router, the default mode is "EQUALS". So with something like attach("/test", ...), only URL /test will match.
The attach method returns a template route on which you can change this matching:
TemplateRoute route = component.getDefaultHost().attach(
"/test", new Restlet() {
#Override
public void handle(Request request, Response response) {
response.setEntity("test", MediaType.TEXT_PLAIN);
}
});
// Default matching mode
int defaultMatching = route.getMatchingMode();
// Set another matching mode
route.setMatchingMode(Template.MODE_EQUALS);
In fact, it's more usually to implement a Restlet application and attach it to a virtual host on the component. In this case, you will have exact matching.
Here is the way to do:
Component component = new Component();
(...)
MyRestletApplication application = new MyRestletApplication();
component.getDefaultHost().attachDefault(application);
Here is a sample content for the application:
public class MyRestletApplication extends Application {
#Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
TemplateRoute route = router.attach("/test", MyServerResource.class);
// Default matching mode
int defaultMatching = route.getMatchingMode();
return router;
}
}
And the content of the server resource:
public class MyServerResource extends ServerResource {
#Get
public String test() throws Exception {
return "test";
}
}
Hope it helps you,
Thierry

JAX-RS: How to secure REST endpoints?

I am using JBoss AS and JAX-RS for creating REST endpoints.
Lets say my class looks like
#Path("/users")
public class UserResource {
#GET
public Response getAccount() {
return "hello";
}
}
Now getAccount is not authenticated at the moment
Wanted
- I would like to add authentication so that when code hits getAccount the user is authenticated
- I would like the authentication to be driven by annotations instead of XML configurations, if at all possible
- I would like to do the database comparison to see if the user is valid
Problem
- I have never done that so I have no idea how to implement it
- I have googled around a lot and found Jersey examples
UPDATE
- I would like to send authentication credentials with each request and not creating any session
Please guide me with one simple working example and I would try to extend from there
You need is a Stateless Spring Security configuration in front of your JAX RS end points.
I have addressed exact problem you are trying to solve but I don't have my own code to share..
Here is one project which has done the exact thing you are asking, Some wise man has done it all for you ;)
https://github.com/philipsorst/angular-rest-springsecurity
What is the magic ?
You have one unprotected URL which does the Authentication, and set the user roles as well..
Then you return some kind of Token, put it some where in cache which will be expected on every subsequent call..
Upon new request on other protected resources, you will check if the Token is present in your cache/session store ( you need some mechanism to keep track of valid tokens )
If token is resent and valid, you do the programmatic Log-in in Spring Security which ensures that you can use all the Security features spring provides, ( Annotations, JSTL Tags etc.. ) !
Once passed token validation you will get the logged in user details in your controllers ( aka JAX RS resources ) to deal with security further..
If the token was not valid or not present , it would be trapped by failure end point which would return appropriate response ( 401 )
Refer Following Link To Understand How Stateless Spring Security is configured..,
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/resources/context.xml
See how a user is validated for the first time and a token is generated..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/resources/UserResource.java
Here is the class where programmatic login is performed on every request after token
check..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/AuthenticationTokenProcessingFilter.java
I solved this with following code.
note Token mechanism will be updated once I do that
I have solved this by modifying the interceptor I have, the following is code
Annotation
#Inherited
#InterceptorBinding
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface SecurityChecked {
}
Resource Class
public class SecureResource {
#GET
#SecurityChecked
public Response getUser() {
return Response.ok("authenticated successfully!").build();
}
}
Interceptor class
#Interceptor
#Provider
#ServerInterceptor
#SecurityChecked
public class SecurityCheckInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityCheckInterceptor.class);
#Nullable
#Override
public ServerResponse preProcess(final HttpRequest request, final ResourceMethod method) throws Failure, WebApplicationException {
final List<String> authToken = request.getHttpHeaders().getRequestHeader("X-AUTH");
if (authToken == null || !isValidToken(authToken.get(0))) {
final ServerResponse serverResponse = new ServerResponse();
serverResponse.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
return serverResponse;
}
return null;
}
private static boolean isValidToken(#Nonnull final String authToken) {
LOGGER.info("validating token: " + authToken);
return true;
}
#SuppressWarnings("rawtypes")
#Override
public boolean accept(final Class declaring, final Method method) {
// return declaring.isAnnotationPresent(SecurityChecked.class); // if annotation on class
return method.isAnnotationPresent(SecurityChecked.class);
}
}
and then I run my Integration tests by deploying the resource class in JBoss and issuing following commands on command-line
curl --header 'X-AUTH: 1a629d035831feadOOO4uFReLyEW8aTmrCS' http://localhost:8080/market-1.0-SNAPSHOT/rest/login
curl --header 'InvalidHeader: InvalidHeaderValue' http://localhost:8080/market-1.0-SNAPSHOT/rest/login

Categories

Resources