Play Framework - Proxy request with session authentication - java

I have a couchapp that I want to control access to with Play! on a per user basis. My plan is to host the couchapp on port xxxx, which is only accessable internally, and host Play! on port 80.
In Apache I would do it like this,
ProxyPass /couchapp http://localhost:xxxx
ProxyPassReverse /couchapp http://localhost:xxxx
But there is no authentication with this approach. I see Play! has some proxy features, but I don't see anyway to add user authentication to this, http://www.playframework.com/documentation/2.0/HTTPServer
Any idea how to add user authentication to a Play! proxy? The code would look something like this.
// Routes all request to http://localhost:xxxx/ if authenticated
public static Result useProxy() {
if (!session("authorized").equals("true")) {
String pingURL = "";
return redirect(pingURL); // will call pingCallback after login
}
return ok(); // take the original request to /couchapp/xxxx.asset and proxy it to http://localhost:xxxx/xxxx.asset
}
public static Result pingCallback() {
Form<PingResponse> pingResponseForm = Form.form(PingResponse.class);
PingResponse pingResponse = pingResponseForm.bindFromRequest().get();
if (!pingResponse.isAuthorized()) {
return unauthorized();
} else {
session("authorized", "true");
}
return ok(); // take the original request to /couchapp/xxxx.asset and proxy it to http://localhost:xxxx/xxxx.asset
}
Thanks!

Have you tried adding:
-Dhttp.proxyUser=username -Dhttp.proxyPassword=password

I used play.libs.WS to make the proxy calls pragmatically. Here's the code. Currently the session is getting lost on every call, but that's a different issue.
-Edit - The session getting lost happens because the fav.ico doesn't have a cookie sent with it and Play relies on cookies for the session. I added a check for that, but it's probably better to filter that out in the routes file.
package controllers;
import models.PingResponse;
import play.data.Form;
import play.libs.F;
import play.mvc.Controller;
import play.mvc.Result;
import play.libs.WS;
public class Ping extends Controller {
final static String playProxyURL = "http://localhost:9000/"; // pretend this is our proxy domain(should be on port 80)
final static String couchAppURL = "http://localhost:80/couchappTest/"; // pretend this is our internal secure site
final static String pingURL = "http://localhost:80/pingTest/"; // pretend this is ping endpoint
public static Result init() {
return Ping.useProxy("");
}
public static Result useProxy(String assetPath) {
// request for favicon.ico doesn't include cookie :(
if (assetPath.equals("favicon.ico")) {
return ok();
}
if (session("authorized") == null || !session("authorized").equals("true")) {
System.out.println("not auth");
return redirect(pingURL);
} else {
return async(
WS.url(couchAppURL + assetPath).get().map(
new F.Function<WS.Response, Result>() {
public Result apply(WS.Response response) {
return ok(response.getBody()).as(response.getHeader("Content-type"));
}
}
)
);
}
}
public static Result pingCallbackGET(String token, String httpRef) {
if (token == null || token.equals("")) {
return unauthorized();
} else {
System.out.println("auth");
session("authorized", "true");
session("token", token);
}
return redirect(playProxyURL + httpRef);
}
}

Related

How to differentiate headers between two/multiple endpoints in a RequestInterceptor

Hello I'm new to Java and Springboot. I'm currently working with an API where before making a POST request, I would need to generate a Bearer token. In order to generate a Bearer token, I would need to pass in my basic auth credentials to the "/oauth/token" endpoint. My application is having trouble passing my basic auth credentials since by the time I hit the "/v1/some-endpoint", I'm denied authorization because the Bearer token is null.
Here's my initial solution thinking I could check the url in the interceptor, then executing the following line but after debugging, it doesn't seem to be hitting that line.
Is there something I'm missing or not implementing correctly? Am I not implementing the Basic Auth endpoint correctly? Let me know if you need more information. Thanks
#Profile("!offline")
#FeignClient(
value = "someClient",
url = "${someProperty.url}",
configuration = SomeClient.SomeClientConfig.class)
public interface someClient {
#PostMapping("/v1/some-endpoint")
void redeemSomething(someRequestBody data);
#PostMapping("/oauth/token")
static BasicAuthResponse getBasicAuthToken() {
return new BasicAuthResponse();
}
#AllArgsConstructor
class SomeClientConfig extends BaseClientConfig {
private final SomeProperties properties;
private final SomeAuthTokenSupplier tokenSupplier = new SomeAuthTokenSupplier();
#Bean
#Override
public CloseableHttpClient apacheClient() {
return apacheClientFactory(properties.getUseProxy());
}
#Bean
public RequestInterceptor someAuthInterceptor() {
return template -> {
if(template.url().equals("/oauth/token")) {
String authToken = Base64Utils.encodeToString((properties.getCredentials().getUser() + ":" + properties.getCredentials().getUser()).getBytes(Charset.forName("UTF-8")));
template.header("Authorization", authToken);
}
template.header("Authorization", String.format("Bearer %s", tokenSupplier.getToken()));
};
}
private class SomeAuthTokenSupplier {
private volatile String token;
private volatile long retrievedOn = -1L;
String getToken() {
if (updateTokenRequired()) {
synchronized (this) {
if (updateTokenRequired()) {
BasicAuthResponse tokenResponse = getBasicAuthToken();
token = tokenResponse.getAccess_token(); // new token from some api should be assigned here
retrievedOn = Instant.now().toEpochMilli();
}
}
}
return token;
}
private boolean updateTokenRequired() {
return token == null || LocalDateTime.now().minusHours(8L).isAfter(LocalDateTime.ofInstant(Instant.ofEpochMilli(retrievedOn), ZoneId.systemDefault()));
}
}
#Override
public Retryer retryer() {
return new ClientRetry(250L, 2, 3) {
#Override
public void continueOrPropagate(RetryableException e) {
if (e.status() == 401 || e.status() == 403) {
tokenSupplier.token = null;
}
super.continueOrPropagate(e);
}
};
}
}
}
It worth using standard Spring Security OAuth2 Client feature instead in order to support authorization in Feign clients
See docs and code samples: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client
UPD
See another code sample: https://github.com/int128/feign-oauth2-example
If several service endpoints require different authentication, then it's worth having several Feign clients, each with own configuration

How can I configure Lagom framework to work with CORS?

How can I configure Lagom framework to work with CORS request (method request 'options').
I have enabled CORS in lagom for one of my projects in this way.
Define a method in service class to handle OPTIONS calls.
ServiceCall<NotUsed, Done> options();
Implement the method in the service-impl class.
#Override
public ServiceCall<NotUsed, Done> options() {
return request -> CompletableFuture.completedFuture(Done.getInstance());
}
Define the options call in the descriptor. As an example, assume that the actual call is,
GET /api/v0.1/user
The service descriptor should look like this:
#Override
default Descriptor descriptor() {
// #formatter:off
return named("notification").withCalls(
restCall(Method.GET, "/api/v0.1/user", this::getUser),
restCall(Method.OPTIONS, "/api/v0.1/user", this::options)
).withAutoAcl(true).withHeaderFilter(new CORSHeaderFilter());
// #formatter:on
}
Note that it has a header filter attached using,
.withHeaderFilter(new CORSHeaderFilter())
CORSHeaderFilter Class should look like this.
import com.lightbend.lagom.javadsl.api.transport.HeaderFilter;
import com.lightbend.lagom.javadsl.api.transport.Method;
import com.lightbend.lagom.javadsl.api.transport.RequestHeader;
import com.lightbend.lagom.javadsl.api.transport.ResponseHeader;
public class CORSHeaderFilter implements HeaderFilter {
#Override
public RequestHeader transformClientRequest(RequestHeader request) {
return request;
}
#Override
public RequestHeader transformServerRequest(RequestHeader request) {
return request;
}
#Override
public ResponseHeader transformServerResponse(ResponseHeader response, RequestHeader request) {
ResponseHeader modifiedResponse = response.withHeader("Access-Control-Allow-Origin", "*");
if (Method.OPTIONS.equals(request.method())) {
modifiedResponse = modifiedResponse.withStatus(204).withHeader("Access-Control-Allow-Headers",
"Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With" +
",If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range").
withHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH").
withHeader("Access-Control-Max-Age", "1728000");
}
return modifiedResponse;
}
#Override
public ResponseHeader transformClientResponse(ResponseHeader response, RequestHeader request) {
ResponseHeader modifiedResponse = response.withHeader("Access-Control-Allow-Origin", "*");
if (Method.OPTIONS.equals(request.method())) {
modifiedResponse = modifiedResponse.withStatus(204).withHeader("Access-Control-Allow-Headers",
"Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With" +
",If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range").
withHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH").
withHeader("Access-Control-Max-Age", "1728000");
}
return modifiedResponse;
}
}
Whenever you add a new endpoint, make sure to add the OPTIONS version of it as well.
To allow a Lagom service written in Java to work with CORS, you'll need to implement a CORS filter per Play:
package example.service.impl
import play.filters.cors.CORSFilter;
import play.http.DefaultHttpFilters;
import javax.inject.Inject;
// See https://playframework.com/documentation/2.5.x/CorsFilter
public class MyCORSFilter extends DefaultHttpFilters {
#Inject
public MyCORSFilter(CORSFilter corsFilter) {
super(corsFilter);
}
}
and then in your application.conf, you'll need to add the filter:
play.http.filters = "example.service.impl.MyCORSFilter"
// To properly setup the CORSFilter, please refer to https://playframework.com/documentation/2.5.x/CorsFilter
// This example is only meant to show what's required for Lagom to use CORS.
play.filters.cors {
// review the values of all these settings to fulfill your needs. These values are not meant for production.
pathPrefixes = ["/api"]
allowedOrigins = null
allowedHttpMethods = null
allowedHttpHeaders = null
exposedHeaders = []
supportsCredentials = false
preflightMaxAge = 6 hour
}
For more info, see the example CORS service and the Play docs.

How to prevent sensitive data from being logged by SyncInvoker.post?

I'm sending a request with a following code:
final WebTarget target = client.target(url);
CustomResponse response = target.request(MediaType.APPLICATION_XML_TYPE)
.post(Entity.xml(password), CustomResponse.class);
Authentication is designed to accept username as a part of url while password is sent in request body.
The issue is that password is shown in logs which are generated by class ClientRequest#writeEntity() inside javax.ws.rs library.
So, my question is how force that library not to store password in logs without turning logging off?
Thank you.
Create a custom java.util.logging.Filter and install it on the java.util.logging.Logger that is generating the log record. You can either just omit that log entry by returning false or simply re-write the parameter or message of the log record so that it doesn't contain the password.
import java.util.logging.Filter;
import java.util.logging.LogRecord;
public class PasswordFilter implements Filter {
#Override
public boolean isLoggable(LogRecord record) {
if (isPasswordCallSite(record)) {
//return false; // omit log entry.
//Or hide password.
Object[] params = record.getParameters();
if (params != null && params.length != 0) {
params = params.clone();
String data = String.valueOf(params[0]);
//Write code to find and replace password.
params[0] = data;
record.setParameters(params);
}
}
return true;
}
private boolean isPasswordCallSite(LogRecord r) {
return "foo.bar.ClientRequest".equals(r.getSourceClassName())
&& "writeEntity".equals(r.getSourceMethodName());
}
}

Reading the server name in an Nginx Ring handler working as a Proxy

I am trying to implement a dynamic proxy using nginx-clojure.
In my nginx config file I have the following:
events {
worker_connections 1024;
}
http {
jvm_path 'myjvmpath';
jvm_var nginx_clojure_jar_path 'myNginx_clojure_jar_path';
jvm_var my_java_handler_jar_path '...';
jvm_options "-Djava.class.path=#{nginx_clojure_jar_path}:#{my_java_handler_jar_path}";
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
server {
listen 8080;
location / {
handler_type 'java';
rewrite_handler_name 'com.poc.MyHandler';
resolver 8.8.8.8;
proxy_pass http://$host$uri;
}
}
}
Basically with this I have all requests passing through MyHandler:
package com.poc;
import java.util.Arrays;
import java.util.Map;
import nginx.clojure.java.ArrayMap;
import nginx.clojure.java.Constants;
import nginx.clojure.java.NginxJavaRingHandler;
import static nginx.clojure.MiniConstants.*;
public class MyHandler implements NginxJavaRingHandler {
#Override
public Object[] invoke(Map<String, Object> request) {
if (isRequestForbidden(request)) {
return new Object[] { NGX_HTTP_FORBIDDEN, // http status 404
ArrayMap.create(CONTENT_TYPE, "text/plain"), // headers map
"Forbidden" };
} else {
return Constants.PHRASE_DONE;
}
}
private boolean isRequestForbidden(Map<String, Object> request) {
String serverName = (String) request.get(Constants.SERVER_NAME);
return false;
}
}
The code is trivial: based on the request(on the server name, more specifically) I want to return Constants.PHRASE_DONE(and the request will be sent to the target server) or a response indicating that site is forbidden. The problem I have is that request.get(Constants.SERVER_NAME) is returning empty string, and I was expecting to have the server name here...
Is there anything wrong with the general approach I am taking? I am a newbie with this stuff and would really appreciate any advice on this.
Found the solution so I post it here.
request.get(Constants.SERVER_NAME) does not contain the name of the requested server but the name of the proxy server. What I was looking for is in a header with key "Host". Here is how to read it:
private boolean isRequestForbidden(Map<String, Object> request) {
#SuppressWarnings("unchecked")
Map<String, String> headers = (Map<String, String>) request
.get(Constants.HEADERS);
String host = headers.get("Host");
// ...
}

Spring Security & Facebook OAuth 2.0 Integration with Graph API

Please, at least pseudo (but from working environment not "maybe this should work") application context and controller/filter that will authenticate and/or auto-register Facebook users.
This link: http://blog.kadirpekel.com/2009/11/09/facebook-connect-integration-with-spring-security/ will not do. Actually I will put minus point to anyone who will post it as answer. I spend 2 hours with the thing and I didn't get it to work. I ended bit more bolder and feeling more stupid than usual after this endeavor :-(
I would really like to see OAuth 2.0 solution for facebook connect. And restrict the use of Facebook JavaScript API to absolute minimum.
Following link shows about what I need:
http://www.richardnichols.net/2010/06/implementing-facebook-oauth-2-0-authentication-in-java/
Please post only code to this question. I already got all the advice I can handle.
UPDATE
I have servlet solution and posted answer here if anyone is interested:
Facebook Connect example in JSP (tomcat)
Here's an MVC implementation of facebook OAuth 2.0
The code's in C# and hopefully its similarity with java helps you out.
Controller(Entry point):Controller(in MVC) is the point in the code where the control reaches after someone clicks on the login link.
public ActionResult Authenticate()
{
var oauthFacebook = new FacebookOAuth();
if (Request["code"] == null)
{
//Redirect the user to Facebook for authorization.
Response.Redirect(oauthFacebook.AuthorizationLinkGet());
}
else
{
//Get the access token and secret.
oauthFacebook.AccessTokenGet(Request["code"]);
if (oauthFacebook.Token.Length > 0)
{
//We can now make our api calls
var user = oauthFacebook.GetAttributes();
}
}
}
FacebookOAuth Class
public class FacebookOAuth : Oauth
{
public FacebookOAuth()
{
Authorize = "https://graph.facebook.com/oauth/authorize";
AccessToken = "https://graph.facebook.com/oauth/access_token";
CallbackUrl = "http://<YourURLHere>/Authenticate";
AttributesBaseUrl = "https://graph.facebook.com/me/?access_token=";
ConsumerKey = ConfigurationManager.AppSettings["FacebookConsumerKey"];//Ur Consumer Key goes here
ConsumerSecret = ConfigurationManager.AppSettings["FacebookConsumerSecret"];//Ur Consumer secret goes here
Provider = "Facebook";
}
public override string AuthorizationLinkGet()
{
return
string.Format(
"{0}?client_id={1}&redirect_uri={2}&scope=email,user_education_history,user_location,user_hometown",
Authorize, ConsumerKey, CallbackUrl);
}
public User GetAttributes()
{
string attributesUrl = string.Format("{0}{1}", AttributesBaseUrl, Token);
string attributes = WebRequest(Method.Get, attributesUrl, String.Empty);
var FacebookUser = new JavaScriptSerializer().Deserialize<FacebookUser>(attributes);
return new User()
{
FirstName = FacebookUser.first_name,
MiddleName = FacebookUser.middle_name,
LastName = FacebookUser.last_name,
Locale = FacebookUser.locale,
UserEmail = FacebookUser.email,
AuthProvider = Provider,
AuthToken=Token
};
}
}
OAuth baseclass(Class from which FacebookOAuth derives)
public abstract class Oauth
{
#region Method enum
public enum Method
{
Get,
Post,
Delete
} ;
#endregion
protected string AccessToken;
protected string AttributesBaseUrl;
protected string Authorize;
protected string CallbackUrl;
protected string ConsumerKey;
protected string ConsumerSecret;
public string Provider { get; protected set; }
public string Token { get; set; }
public virtual string AuthorizationLinkGet()
{
return
string.Format(
"{0}?client_id={1}&redirect_uri={2}&scope=publish_stream,email,user_education_history,user_location",
Authorize, ConsumerKey, CallbackUrl);
}
public void AccessTokenGet(string authToken)
{
Token = authToken;
string accessTokenUrl = string.Format("{0}?client_id={1}&redirect_uri={2}&client_secret={3}&code={4}",
AccessToken, ConsumerKey, CallbackUrl, ConsumerSecret, authToken);
string response = WebRequest(Method.Get, accessTokenUrl, String.Empty);
if (response.Length > 0)
{
//Store the returned access_token
NameValueCollection qs = HttpUtility.ParseQueryString(response);
if (qs["access_token"] != null)
{
Token = qs["access_token"];
}
}
}
public string WebRequest(Method method, string url, string postData)
{
StreamWriter requestWriter;
string responseData = string.Empty;
var webRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
if (webRequest != null)
{
webRequest.Method = method.ToString();
webRequest.ServicePoint.Expect100Continue = false;
webRequest.Timeout = 20000;
if (method == Method.Post)
{
webRequest.ContentType = "application/x-www-form-urlencoded";
//POST the data.
requestWriter = new StreamWriter(webRequest.GetRequestStream());
try
{
requestWriter.Write(postData);
}
finally
{
requestWriter.Close();
}
}
responseData = WebResponseGet(webRequest);
}
return responseData;
}
public string WebResponseGet(HttpWebRequest webRequest)
{
StreamReader responseReader = null;
string responseData;
try
{
responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
responseData = responseReader.ReadToEnd();
}
finally
{
if (webRequest != null) webRequest.GetResponse().GetResponseStream().Close();
if (responseReader != null) responseReader.Close();
}
return responseData;
}
}
I actually just finished my non-javascript, implementation of the Facebook Graph API Authentication last night. I was a gargantuan pain in the a**, but it works and it's working fairly well.
I used the example from the link you posted above as a starting point, as well as, the code from here as a starting point. I had to write my own implementation of their FacebookGraphAuthenticationProvider and their FacebookGraphAuthenticationFilter, but now it works the way I want it to.
You need to create implementations of both of these files, put your filter in the filter chain, and create a implementation of the Spring Security UserDetailsService that the Provider can use to manage your user account information. I have some code on my machine at home that I can send you via email if you like.
Here are the steps I had to use to get the authentication to work:
Get an "code" for a user, this is done by making the following call: https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&scope=email,read_stream (The scope is all the permissions you want to request from FB). This call will create an "authentication code" which will then be sent back to your "redirect_uri" (which I stated as http://{my fb app registered domain}/j_spring_security_authentication_check.
Once you have this "code", you need to make a call within your AuthenticationProvider that will retrieve an access_token for your user's session: this URL looks like: https://graph.facebook.com/oauth/access_token? client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE. You have to make sure your "redirect_uri" is the same as the one you did in #1. You'll make the above call using something like Apache's HttpClient, or the like.
Now with this access_token (which comes in the body of above response), you can get your user's profile information with the following URL: https://graph.facebook.com/me?access_token={ACCESS_TOKEN from above). The response will be in JSON. You can also use the access_token with all of the graph API to post status, pictures, etc.
I have some code at home that has my full implementation that I would be happy to share.
I hope this helps at least a bit. I suggest using the Spring Social app to get started with posting status, pictures, wall stuff, etc. This will be a good place to start looking at FB-Spring interaction.

Categories

Resources