Which Google api to use for getting user's first name, last name, picture, etc? - oauth

I have the oauth authorization with google working correctly and am getting data from the contacts api. Now, I want to programmatically get a gmail user's first name, last name and picture. Which google api can i use to get this data?

The contacts API perhaps works, but you have to request permission from the user to access all contacts. If I were a user, that would make me wary of granting the permission (because this essentially gives you permission to spam all my contacts...)
I found the response here to be useful, and only asks for "basic profile information":
Get user info via Google API
I have successfully used this approach, and can confirm it returns the following Json object:
{
"id": "..."
"email": "...",
"verified_email": true,
"name": "....",
"given_name": "...",
"family_name": "...",
"link": "...",
"picture": "...",
"gender": "male",
"locale": "en"
}

Use this code to get firstName and lastName of a google user:
final HttpTransport transport = new NetHttpTransport();
final JsonFactory jsonFactory = new JacksonFactory();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(clientId))
.setIssuer("https://accounts.google.com")
.build();
GoogleIdToken idToken = null;
try {
idToken = verifier.verify(googleAuthenticationPostResponse.getId_token());
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
GoogleIdToken.Payload payload = null;
if (idToken != null) {
payload = idToken.getPayload();
}
String firstName = payload.get("given_name").toString();
String lastName = payload.get("family_name").toString();

If you're using the google javascript API, you can use the new "auth2" API after authenticating to get the user's profile, containing:
name
email
image URL
https://developers.google.com/identity/sign-in/web/reference#googleusergetbasicprofile

For the picture, you can use the Google contacts Data API too: see http://code.google.com/intl/fr/apis/contacts/docs/3.0/developers_guide_protocol.html#retrieving_photo

The simplest way to get this information would be from the Google + API. Specifically the
https://developers.google.com/+/api/latest/people/get
When using the api use the following HTTP GET:
GET https://www.googleapis.com/plus/v1/people/me
This will return all of the above information requested from the user.

I found the answer while looking around in the contacts API forum. When you get the result-feed, just do the following in Java-
String Name = resultFeed.getAuthors().get(0).getName();
String emailId = resultFeed.getId();
I am still looking for a way to get the user profile picture.

Use this Code for Access Google Gmail Login Credential oAuth2 :
Class Name : OAuthHelper
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map.Entry;
import java.util.SortedSet;
import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
import oauth.signpost.commonshttp.HttpRequestAdapter;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import oauth.signpost.http.HttpParameters;
import oauth.signpost.signature.HmacSha1MessageSigner;
import oauth.signpost.signature.OAuthMessageSigner;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.util.Log;
public class OAuthHelper {
private static final String TAG = "OAuthHelper";
private OAuthConsumer mConsumer;
private OAuthProvider mProvider;
private String mCallbackUrl;
public OAuthHelper(String consumerKey, String consumerSecret, String scope, String callbackUrl) throws UnsupportedEncodingException {
mConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
mProvider = new CommonsHttpOAuthProvider("https://www.google.com/accounts/OAuthGetRequestToken?scope=" + URLEncoder.encode(scope, "utf-8"), "https://www.google.com/accounts/OAuthGetAccessToken", "https://www.google.com/accounts/OAuthAuthorizeToken?hd=default");
mProvider.setOAuth10a(true);
mCallbackUrl = (callbackUrl == null ? OAuth.OUT_OF_BAND : callbackUrl);
}
public String getRequestToken() throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
String authUrl = mProvider.retrieveRequestToken(mConsumer, mCallbackUrl);
System.out.println("Gautam AUTH URL : " + authUrl);
return authUrl;
}
public String[] getAccessToken(String verifier) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
mProvider.retrieveAccessToken(mConsumer, verifier);
return new String[] { mConsumer.getToken(), mConsumer.getTokenSecret() };
}
public String[] getToken() {
return new String[] { mConsumer.getToken(), mConsumer.getTokenSecret() };
}
public void setToken(String token, String secret) {
mConsumer.setTokenWithSecret(token, secret);
}
public String getUrlContent(String url) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException, IOException {
HttpGet request = new HttpGet(url);
// sign the request
mConsumer.sign(request);
// send the request
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
// get content
BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null)
sb.append(line + NL);
in.close();
System.out.println("gautam INFO : " + sb.toString());
return sb.toString();
}
public String getUserProfile(String t0, String t1, String url) {
try {
OAuthConsumer consumer = new CommonsHttpOAuthConsumer(t0, t1);
HttpGet request = new HttpGet(url);
// sign the request
consumer.sign(request);
// send the request
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
//String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null)
sb.append(line );
in.close();
System.out.println("Gautam Profile : " + sb.toString());
return sb.toString();
} catch (Exception e) {
System.out.println("Error in Geting profile Info : " + e);
return "";
}
}
public String buildXOAuth(String email) {
String url = String.format("https://mail.google.com/mail/b/%s/smtp/", email);
HttpRequestAdapter request = new HttpRequestAdapter(new HttpGet(url));
// Sign the request, the consumer will add any missing parameters
try {
mConsumer.sign(request);
} catch (OAuthMessageSignerException e) {
Log.e(TAG, "failed to sign xoauth http request " + e);
return null;
} catch (OAuthExpectationFailedException e) {
Log.e(TAG, "failed to sign xoauth http request " + e);
return null;
} catch (OAuthCommunicationException e) {
Log.e(TAG, "failed to sign xoauth http request " + e);
return null;
}
HttpParameters params = mConsumer.getRequestParameters();
// Since signpost doesn't put the signature into params,
// we've got to create it again.
OAuthMessageSigner signer = new HmacSha1MessageSigner();
signer.setConsumerSecret(mConsumer.getConsumerSecret());
signer.setTokenSecret(mConsumer.getTokenSecret());
String signature;
try {
signature = signer.sign(request, params);
} catch (OAuthMessageSignerException e) {
Log.e(TAG, "invalid oauth request or parameters " + e);
return null;
}
params.put(OAuth.OAUTH_SIGNATURE, OAuth.percentEncode(signature));
StringBuilder sb = new StringBuilder();
sb.append("GET ");
sb.append(url);
sb.append(" ");
int i = 0;
for (Entry<String, SortedSet<String>> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue().first();
int size = entry.getValue().size();
if (size != 1)
Log.d(TAG, "warning: " + key + " has " + size + " values");
if (i++ != 0)
sb.append(",");
sb.append(key);
sb.append("=\"");
sb.append(value);
sb.append("\"");
}
Log.d(TAG, "xoauth encoding " + sb);
Base64 base64 = new Base64();
try {
byte[] buf = base64.encode(sb.toString().getBytes("utf-8"));
return new String(buf, "utf-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "invalid string " + sb);
}
return null;
}
}
//===================================
Create : WebViewActivity.class
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.Window;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class WebViewActivity extends Activity {
//WebView webview;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_PROGRESS);
WebView webview = new WebView(this);
webview.getSettings().setJavaScriptEnabled(true);
setContentView(webview);
// Load the page
Intent intent = getIntent();
if (intent.getData() != null) {
webview.loadUrl(intent.getDataString());
}
webview.setWebChromeClient(new WebChromeClient() {
// Show loading progress in activity's title bar.
#Override
public void onProgressChanged(WebView view, int progress) {
setProgress(progress * 100);
}
});
webview.setWebViewClient(new WebViewClient() {
// When start to load page, show url in activity's title bar
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
setTitle(url);
if (url.startsWith("my-activity")) {
Intent result = new Intent();
System.out.println("Gautam my-activity : " + url);
result.putExtra("myurl", url);
setResult(RESULT_OK, result);
finish();
}
}
#Override
public void onPageFinished(WebView view, String url) {
System.out.println("Gautam Page Finish...");
CookieSyncManager.getInstance().sync();
// Get the cookie from cookie jar.
String cookie = CookieManager.getInstance().getCookie(url);
System.out.println("Gautam Cookie : " + cookie);
if (cookie == null) {
return;
}
// Cookie is a string like NAME=VALUE [; NAME=VALUE]
String[] pairs = cookie.split(";");
for (int i = 0; i < pairs.length; ++i) {
String[] parts = pairs[i].split("=", 2);
// If token is found, return it to the calling activity.
System.out.println("Gautam=> "+ parts[0] + " = " + parts[1]);
if (parts.length == 2 && parts[0].equalsIgnoreCase("oauth_token")) {
Intent result = new Intent();
System.out.println("Gautam AUTH : " + parts[1]);
result.putExtra("token", parts[1]);
setResult(RESULT_OK, result);
finish();
}
}
}
});
}
}
//=========================
Call From : MainActivity.class
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.http.HttpResponse;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener{
Button btnLogin;
OAuthHelper MyOuthHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnLogin = (Button)findViewById(R.id.btnLogin);
btnLogin.setOnClickListener(this);
}
#Override
protected void onResume() {
/*System.out.println("On Resume call ");
try {
String[] token = getVerifier();
if (token != null){
String accessToken[] = MyOuthHelper.getAccessToken(token[1]);
}
} catch (Exception e) {
System.out.println("gautam error on Resume : " + e);
}*/
super.onResume();
}
private String[] getVerifier(String url) {
// extract the token if it exists
Uri uri = Uri.parse(url);
if (uri == null) {
return null;
}
String token = uri.getQueryParameter("oauth_token");
String verifier = uri.getQueryParameter("oauth_verifier");
return new String[] { token, verifier };
}
#Override
public void onClick(View v) {
try {
MyOuthHelper = new OAuthHelper("YOUR CLIENT ID", "YOUR SECRET KEY", "https://www.googleapis.com/auth/userinfo.profile", "my-activity://localhost");
} catch (Exception e) {
System.out.println("gautam errorin Class call : " + e);
}
try {
String uri = MyOuthHelper.getRequestToken();
Intent intent = new Intent(MainActivity.this, WebViewActivity.class);
intent.setData(Uri.parse(uri));
startActivityForResult(intent, 0);
/* startActivity(new Intent("android.intent.action.VIEW",
Uri.parse(uri)));*/
} catch (Exception e) {
System.out.println("Gautm Error in getRequestTokan : " + e);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 0:
if (resultCode != RESULT_OK || data == null) {
return;
}
// Get the token.
String url = data.getStringExtra("myurl");
try {
String[] token = getVerifier(url);
if (token != null){
String accessToken[] = MyOuthHelper.getAccessToken(token[1]);
System.out.println("Gautam Final [0] : " + accessToken[0] + " , [1] : " + accessToken[1]);
//https://www.googleapis.com/oauth2/v1/userinfo?alt=json
// String myProfile = MyOuthHelper.getUserProfile(accessToken[0], accessToken[1], "https://www.googleapis.com/oauth2/v1/userinfo?alt=json");
String myProfile = MyOuthHelper.getUrlContent("https://www.googleapis.com/oauth2/v1/userinfo?alt=json");
}
} catch (Exception e) {
System.out.println("gautam error on Resume : " + e);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
}
//=================================
And Finally Your Profile Information coming, Just Look in your Logcat message print.
Note : Not Forgot to put Internet Permission in Manifest File
And Your App Register in Google Console for Client ID and Secret Key
For App Registration Please Looking this Step : App Registration Step

Related

Swagger 2 Feign client code oAuth flow throwing error url values must be not be absolute

I exposed Rest APIs, and I generated client code using Swagger 2 Java language with Feign library. The code gen generated the below OAuth RequestInterceptor. I am getting the below error when I use the oAuth as auth.
Error
feign.RetryableException: url values must be not be absolute.
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:95)
at com.sam.feign.auth.OAuth.apply(OAuth.java:83)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:161)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:110)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy9.getUser(Unknown Source)
at com.sam.feign.clients.UserApiTest.getUserTest(UserApiTest.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.IllegalArgumentException: url values must be not be absolute.
at feign.RequestTemplate.uri(RequestTemplate.java:434)
at feign.RequestTemplate.uri(RequestTemplate.java:421)
at feign.RequestTemplate.append(RequestTemplate.java:388)
at com.sam.feign.auth.OAuth$OAuthFeignClient.execute(OAuth.java:163)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:65)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:55)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:71)
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:93)
... 34 more
Swagger Generated oAuth supporting file
public class OAuth implements RequestInterceptor {
static final int MILLIS_PER_SECOND = 1000;
public interface AccessTokenListener {
void notify(BasicOAuthToken token);
}
private volatile String accessToken;
private Long expirationTimeMillis;
private OAuthClient oauthClient;
private TokenRequestBuilder tokenRequestBuilder;
private AuthenticationRequestBuilder authenticationRequestBuilder;
private AccessTokenListener accessTokenListener;
public OAuth(Client client, TokenRequestBuilder requestBuilder) {
this.oauthClient = new OAuthClient(new OAuthFeignClient(client));
this.tokenRequestBuilder = requestBuilder;
}
public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));
switch(flow) {
case accessCode:
case implicit:
tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
break;
case password:
tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
break;
case application:
tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
break;
default:
break;
}
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
}
public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes);
}
#Override
public void apply(RequestTemplate template) {
// If the request already have an authorization (eg. Basic auth), do nothing
if (template.headers().containsKey("Authorization")) {
return;
}
// If first time, get the token
if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
updateAccessToken(template);
}
if (getAccessToken() != null) {
template.header("Authorization", "Bearer " + getAccessToken());
}
}
public synchronized void updateAccessToken(RequestTemplate template) {
OAuthJSONAccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
} catch (Exception e) {
throw new RetryableException(400, e.getMessage(), template.request().httpMethod(), e, null, template.request());
}
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
if (accessTokenListener != null) {
accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
}
}
}
public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
this.accessTokenListener = accessTokenListener;
}
public synchronized String getAccessToken() {
return accessToken;
}
public synchronized void setAccessToken(String accessToken, Long expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}
public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
return authenticationRequestBuilder;
}
public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
this.authenticationRequestBuilder = authenticationRequestBuilder;
}
public OAuthClient getOauthClient() {
return oauthClient;
}
public void setOauthClient(OAuthClient oauthClient) {
this.oauthClient = oauthClient;
}
public void setOauthClient(Client client) {
this.oauthClient = new OAuthClient( new OAuthFeignClient(client));
}
public static class OAuthFeignClient implements HttpClient {
private Client client;
public OAuthFeignClient() {
this.client = new Client.Default(null, null);
}
public OAuthFeignClient(Client client) {
this.client = client;
}
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
RequestTemplate req = new RequestTemplate()
.append(request.getLocationUri())
.method(requestMethod)
.body(request.getBody());
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
public void shutdown() {
// Nothing to do here
}
}
}
ApiClient.java have the below absolute URL which configured in swagger spec
public ApiClient() {
objectMapper = createObjectMapper();
apiAuthorizations = new LinkedHashMap<String, RequestInterceptor>();
feignBuilder = Feign.builder()
.encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
.decoder(new JacksonDecoder(objectMapper))
.logger(new Slf4jLogger());
}
public ApiClient(String[] authNames) {
this();
for(String authName : authNames) {
RequestInterceptor auth = null;
if ("client-credentils-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.application, "", "http://localhost:8080/app/oauth/token", "user.create");
} else if ("password-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.password, "", "http://localhost:8080/app/oauth/token", "openid");
} else {
throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
}
addAuthorization(authName, auth);
}
}
Used the below dependencies
swagger-codegen-maven-plugin v2.4.28
feign-version 11.6
feign-form-version 3.8.0
oltu-version 1.0.1
Java 8
I am invoking the client by using below code
UserApi api = new ApiClient("client-credentils-oauth2","admin", "admin", null, null).buildClient(UserApi.class);
api.getUser(login, tenant)
I made the few changes in the generated oAuth.java file to make it work. Expecting the client generated code should work without making any manual changes.
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
// Added the below 3 lines
URI targetUri = URI.create(uri);
String target = targetUri.getScheme() + "://" + targetUri.getAuthority() ;
String path = targetUri.getPath();
RequestTemplate req = new RequestTemplate()
.uri(path)
.method(requestMethod)
.body(request.getBody())
.target(target); // Added this line
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
req = req.resolve(new HashMap<String, Object>()); // Added this line
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
Appreciate if someone can assist me with this issue

Return value from Jenkins plugin

I'm developing a Jenkins plugin and in one of my build steps, I need to return a value. In this build step, I'm sending an API call to generate a registration token and I want to return that token as the output of this build step. The idea is to use this generated token later in pipeline/free-style.
My question is, how do I do that?
Here's my Build class:
package **.********.plugins;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Builder;
import hudson.util.ListBoxModel;
import **.********.constants.Constants;
import **.********.helpers.ApiHelper;
import **.********.helpers.ApiResponse;
import **.********.helpers.LogHelper;
import **.********.model.AgentDockerConfigData;
import **.********.model.AgentDockerConfigGenerationRequestData;
import **.********.model.JobData;
import **.********.model.ProjectData;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.HashMap;
public class GenerateAgentConfigToken extends Builder implements SimpleBuildStep {
//region Private members
private ApiHelper apiHelper;
private String alias;
private String projectId;
private String jobId;
private String browsers;
private AgentDockerConfigData config;
//endregion
//region Constructors
public GenerateAgentConfigToken() { }
#DataBoundConstructor
public GenerateAgentConfigToken(String alias, String projectId, String jobId, String browsers) {
this.alias = alias;
this.projectId = projectId;
this.jobId = jobId;
this.browsers = browsers;
}
//endregion
//region Setters & Getters
public String getAlias() {
return alias;
}
#DataBoundSetter
public void setAlias(String alias) {
this.alias = alias;
}
public String getProjectId() {
return projectId;
}
#DataBoundSetter
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public String getJobId() {
return jobId;
}
#DataBoundSetter
public void setJobId(String jobId) {
this.jobId = jobId;
}
public String getBrowsers() {
return browsers;
}
#DataBoundSetter
public void setBrowsers(String browsers) {
this.browsers = browsers;
}
//endregion
private void init() {
LogHelper.Debug("Initializing API helper...");
this.apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
}
#Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
#Override
public void perform(#Nonnull Run<?, ?> run, #Nonnull FilePath filePath, #Nonnull Launcher launcher, #Nonnull TaskListener taskListener) throws InterruptedException, IOException {
try {
EnvVars envVars = new EnvVars();
envVars = run.getEnvironment(taskListener);
envVars.put("jobId", jobId);
init();
LogHelper.SetLogger(taskListener.getLogger(), PluginConfiguration.DESCRIPTOR.isVerbose());
generateAgentConfigToken();
} catch (Exception e) {
LogHelper.Error(e);
run.setResult(Result.FAILURE);
}
}
private void generateAgentConfigToken() throws IOException {
LogHelper.Info("Sending a request to generate agent configuration token...");
//TODO: Change the URL to the production URL
ApiResponse<AgentDockerConfigData> response = apiHelper.Post(
Constants.TP_GENERATE_AGENT_CONFIG_TOKEN_URL,
null,
null,
generateRequestBody(),
AgentDockerConfigData.class);
if (response.isSuccessful()) {
if (response.getData() != null) {
config = response.getData();
}
} else {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to generate agent configuration token" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
}
private AgentDockerConfigGenerationRequestData generateRequestBody() {
// if the user did not provide an alias and jobId, send the body as null
if (StringUtils.isEmpty(alias) && StringUtils.isEmpty(jobId))
return null;
AgentDockerConfigGenerationRequestData body = new AgentDockerConfigGenerationRequestData();
if (!StringUtils.isEmpty(alias))
body.setAlias(alias);
if (!StringUtils.isEmpty(jobId)) {
body.setJobId(jobId);
if (!StringUtils.isEmpty(browsers))
body.setBrowsers(browsers.split(","));
}
return body;
}
#Override
public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); }
#Extension
#Symbol(Constants.TP_GENERATE_AGENT_CONFIG_TOKEN_SYMBOL)
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
public DescriptorImpl() {
load();
}
#Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
req.bindJSON(this, formData);
save();
return super.configure(req, formData);
}
#Override
public boolean isApplicable(#SuppressWarnings("rawtypes") Class<? extends AbstractProject> jobType) {
return true;
}
#Nonnull
#Override
public String getDisplayName() {
return Constants.TP_GENERATE_AGENT_CONFIG_TOKEN;
}
public ListBoxModel doFillProjectIdItems() {
HashMap<String, Object> headers = new HashMap<>();
headers.put(Constants.ACCEPT, Constants.APPLICATION_JSON);
ApiResponse<ProjectData[]> response = null;
try {
ApiHelper apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
response = apiHelper.Get(Constants.TP_RETURN_ACCOUNT_PROJECTS, headers, ProjectData[].class);
if (!response.isSuccessful()) {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to fetch the projects list" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
ListBoxModel model = new ListBoxModel();
model.add("Select a project", "");
for (ProjectData project : response.getData()) {
model.add(
project.getName() + " [" + project.getId() + "]",
project.getId());
}
return model;
} catch (IOException | NullPointerException e) {
LogHelper.Error(e);
}
return null;
}
public ListBoxModel doFillJobIdItems(#QueryParameter String projectId) {
if (projectId.isEmpty()) {
return new ListBoxModel();
}
HashMap<String, Object> headers = new HashMap<>();
headers.put(Constants.ACCEPT, Constants.APPLICATION_JSON);
ApiResponse<JobData[]> response = null;
try {
ApiHelper apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
response = apiHelper.Get(String.format(Constants.TP_RETURN_PROJECT_JOBS, projectId), headers, JobData[].class);
if (!response.isSuccessful()) {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to fetch the project's jobs list" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
ListBoxModel model = new ListBoxModel();
model.add("Select a job to execute from the selected project (You must select a project first)", "");
for (JobData job : response.getData()) {
model.add(
job.getName() + " [" + job.getId() + "]",
job.getId());
}
return model;
} catch (IOException | NullPointerException e) {
LogHelper.Error(e);
}
return null;
}
}
}
Is there a way to return anything from perform rather than void or boolean ?

Feign ErrorDecoder : retrieve the original message

I use a ErrorDecoder to return the right exception rather than a 500 status code.
Is there a way to retrieve the original message inside the decoder. I can see that it is inside the FeignException, but not in the decode method. All I have is the 'status code' and a empty 'reason'.
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
switch (response.status()) {
case 404:
return new FileNotFoundException("File no found");
case 403:
return new ForbiddenAccessException("Forbidden access");
}
return errorDecoder.decode(s, response);
}
}
Here the original message : "message":"Access to the file forbidden"
feign.FeignException: status 403 reading ProxyMicroserviceFiles#getUserRoot(); content:
{"timestamp":"2018-11-28T17:34:05.235+0000","status":403,"error":"Forbidden","message":"Access to the file forbidden","path":"/root"}
Also I use my FeignClient interface like a RestController so I don't use any other Controler populated with the proxy that could encapsulate the methods calls.
#RestController
#FeignClient(name = "zuul-server")
#RibbonClient(name = "microservice-files")
public interface ProxyMicroserviceFiles {
#GetMapping(value = "microservice-files/root")
Object getUserRoot();
#GetMapping(value = "microservice-files/file/{id}")
Object getFileById(#PathVariable("id") int id);
}
Here is a solution, the message is actually in the response body as a stream.
package com.clientui.exceptions;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.*;
import java.io.*;
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
String message = null;
Reader reader = null;
try {
reader = response.body().asReader();
//Easy way to read the stream and get a String object
String result = CharStreams.toString(reader);
//use a Jackson ObjectMapper to convert the Json String into a
//Pojo
ObjectMapper mapper = new ObjectMapper();
//just in case you missed an attribute in the Pojo
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//init the Pojo
ExceptionMessage exceptionMessage = mapper.readValue(result,
ExceptionMessage.class);
message = exceptionMessage.message;
} catch (IOException e) {
e.printStackTrace();
}finally {
//It is the responsibility of the caller to close the stream.
try {
if (reader != null)
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
switch (response.status()) {
case 404:
return new FileNotFoundException(message == null ? "File no found" :
message);
case 403:
return new ForbiddenAccessException(message == null ? "Forbidden
access" : message);
}
return errorDecoder.decode(s, response);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public static class ExceptionMessage{
private String timestamp;
private int status;
private String error;
private String message;
private String path;
}
}
If you want to get the response payload body, with the Feign exception, just use this method:
feignException.contentUTF8();
Example:
try {
itemResponse = call(); //method with the feign call
} catch (FeignException e) {
logger.error("ResponseBody: " + e.contentUTF8());
}
It is suggested to use input stream instead of reader and map it to your object.
package com.clientui.exceptions;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.*;
import java.io.*;
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
String message = null;
InputStream responseBodyIs = null;
try {
responseBodyIs = response.body().asInputStream();
ObjectMapper mapper = new ObjectMapper();
ExceptionMessage exceptionMessage = mapper.readValue(responseBodyIs, ExceptionMessage.class);
message = exceptionMessage.message;
} catch (IOException e) {
e.printStackTrace();
// you could also return an exception
return new errorMessageFormatException(e.getMessage());
}finally {
//It is the responsibility of the caller to close the stream.
try {
if (responseBodyIs != null)
responseBodyIs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
switch (response.status()) {
case 404:
return new FileNotFoundException(message == null ? "File no found" :
message);
case 403:
return new ForbiddenAccessException(message == null ? "Forbidden access" : message);
}
return errorDecoder.decode(s, response);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public static class ExceptionMessage{
private String timestamp;
private int status;
private String error;
private String message;
private String path;
}
}
If you're like me and really just want the content out of a failed Feign call without all these custom decoders and boilerplate, there is a hacky way do this.
If we look at FeignException when it is being created and a response body exists, it assembles the exception message like so:
if (response.body() != null) {
String body = Util.toString(response.body().asReader());
message += "; content:\n" + body;
}
Therefore if you're after the response body, you can just pull it out by parsing the Exception message since it is delimited by a newline.
String[] feignExceptionMessageParts = e.getMessage().split("\n");
String responseContent = feignExceptionMessageParts[1];
And if you want the object, you can use something like Jackson:
MyResponseBodyPojo errorBody = objectMapper.readValue(responseContent, MyResponseBodyPojo.class);
I do not claim this is a smart approach or a best practice.
The original message is within the Response body, as already answered. However, we can reduce the amount of boilerplate using Java 8 Streams to read it:
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
String body = "4xx client error";
try {
body = new BufferedReader(response.body().asReader(StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
} catch (IOException ignore) {}
switch (response.status()) {
case 404:
return new FileNotFoundException(body);
case 403:
return new ForbiddenAccessException(body);
}
return errorDecoder.decode(s, response);
}
}
Some refactoring and code style on accepted answer:
#Override
#SneakyThrows
public Exception decode(String methodKey, Response response) {
String message;
try (Reader reader = response.body().asReader()) {
String result = StringUtils.toString(reader);
message = mapper.readValue(result, ErrorResponse.class).getMessage();
}
if (response.status() == 401) {
return new UnauthorizedException(message == null ? response.reason() : message);
}
if (response.status() == 403) {
return new ForbiddenException(message == null ? response.reason() : message);
}
return defaultErrorDecoder.decode(methodKey, response);
}

oAuth SSO on Liferay 6.2

I need to integrate on Liferay 6.2 GA6 a SSO from a web application that provide info by oAuth
A native support doesn't exist.
My problem is to create the automatic login on Liferay (after the user creation or if the user already exists). Any help ?
You have to create a hook where you create an AutoLogin class that extends BaseAutoLogin. Read the oAuth documentation and write a login logic in that hook, then set it in auto.login.hooks property in portal-ext.properties(properties reference). Then you will have to create a filter that extends BasePortalFilter and implemets processFilter method. You can model on CASFilter and CASAutologin
override portal.properties adding
auto.login.hooks=com.yourpackage.hook.MyAutoLogin
Create the class:
package com.yourpackage.hook;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.model.User;
import com.liferay.portal.security.auth.AutoLogin;
import com.liferay.portal.security.auth.AutoLoginException;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.util.PortalUtil;
//based on example
// https://bitbucket.org/brandizzi/liferay-examples/src/a41d71eba8f2fb2d4272a3ce8f393e77cec41d60/unsafe-login-hook/docroot/WEB-INF/src/br/brandizzi/adam/liferay/unsecure/UnsecureAutoLogin.java?at=default&fileviewer=file-view-default
public class MyAutoLogin implements AutoLogin {
#Override
public String[] login(HttpServletRequest request,HttpServletResponse response) throws AutoLoginException {
HttpSession session = request.getSession();
String emailAddress = (String) session.getAttribute("LIFERAY_SHARED_EMAIL");
if (emailAddress == null || emailAddress.isEmpty())
return null;
long companyId = PortalUtil.getCompanyId(request);
User user = null;
try {
user = UserLocalServiceUtil.getUserByEmailAddress(companyId, emailAddress);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
String redirect = ParamUtil.getString(request, "redirect");
if (Validator.isNotNull(redirect)) {
request.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT_AND_CONTINUE,PortalUtil.escapeRedirect(redirect));
}
String[] credentials = new String[3];
credentials[0] = String.valueOf(user.getUserId());
credentials[1] = user.getPassword();
credentials[2] = String.valueOf(user.isPasswordEncrypted());
// credentials[2] = Boolean.FALSE.toString();
return credentials;
}
#Override
public String[] handleException(HttpServletRequest arg0,
HttpServletResponse arg1, Exception arg2)
throws AutoLoginException {
System.out.println("AutoLogin handleException ");
return null;
}
}
create an other class with the static methods:
public static JSONObject doSSO(String firstname, String surname, String email, String username,String accessToken, ActionRequest actionRequest, ActionResponse actionResponse){
JSONObject jsonResp = JSONFactoryUtil.createJSONObject();
//Get default Liferay company
String webId = new String("liferay.com");
Company company = null;
try {
company = CompanyLocalServiceUtil.getCompanyByWebId(webId);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
System.out.println("email "+email);
User currentUser = null;
try {
currentUser = UserLocalServiceUtil.getUserByEmailAddress(company.getCompanyId(), email);
} catch (SystemException | PortalException e) {
System.out.println("User to create");
}
if (Validator.isNull(currentUser)){
long newUserId = 0;
try {
jsonResp = addNewUser( firstname, surname, email, username );
} catch (Exception e) {
e.printStackTrace();
}
String newUserIdS = jsonResp.getString("newUserId");
newUserId = Long.valueOf(newUserIdS);
try {
currentUser = UserLocalServiceUtil.fetchUser(newUserId);
} catch (SystemException e) {
e.printStackTrace();
}
notifyAuthorAboutInvited(email, currentUser);
}
setExistingUserOnSession( actionRequest,currentUser, accessToken);
//Login the user
HttpServletRequest request = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(actionRequest));
HttpServletResponse response = PortalUtil.getHttpServletResponse(actionResponse);
MyAutoLogin myLogin = new MyAutoLogin();
try {
myLogin.login(request, response);
jsonResp.put("message","OK - User logged on Liferay");
} catch (AutoLoginException e1) {
e1.printStackTrace();
}
//set Token on customfield
//remember to set permission guest to view and update
ServiceContext serviceContext = null;
try {
serviceContext = ServiceContextFactory.getInstance(User.class.getName(), actionRequest);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
Map<String,Serializable> expandoBridgeAttributes = new HashMap<String, Serializable>();
expandoBridgeAttributes.put("token", accessToken);
serviceContext.setExpandoBridgeAttributes(expandoBridgeAttributes);
currentUser.setExpandoBridgeAttributes(serviceContext);
try {
UserLocalServiceUtil.updateUser(currentUser);
} catch (SystemException e) {
e.printStackTrace();
}
String userToken =currentUser.getExpandoBridge().getAttribute("token").toString();
//System.out.println("doSSO accessToken dopo "+userToken);
return jsonResp;
}
and:
private static void setExistingUserOnSession(ActionRequest actionRequest,User user, String accessToken) {
HttpServletRequest req = PortalUtil.getHttpServletRequest(actionRequest);
HttpSession session = req.getSession();
session.setAttribute("LIFERAY_SHARED_EMAIL", user.getEmailAddress());
}

Spring security 3 + JCIFS ntlm

Can they work together?
Some project sample would be great.
I have a web-app on Spring3. And i need to implement NTLM. Spring stopped NTLM support in 3rd version. Is there any possibilities to implement it?
Looking for a sample project.
They can be used together. Essentially what you want to do is hook into the SPNEGO protocol and detect when you receive an NTLM packet from the client. A good description of the protocol can be found here:
http://www.innovation.ch/personal/ronald/ntlm.html
http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx
Another great resource for NTLM is this:
http://davenport.sourceforge.net/ntlm.html
But you asked for a sample so here goes. To detect an NTLM packet you need to base64
decode the packet and inspect for a starting string:
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
...
}
public static boolean isNTLMMessage(byte[] token) {
for (int i = 0; i < 8; i++) {
if (token[i] != NTLMSSP_SIGNATURE[i]) {
return false;
}
}
return true;
}
public static final byte[] NTLMSSP_SIGNATURE = new byte[]{
(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M',
(byte) 'S', (byte) 'S', (byte) 'P', (byte) 0
};
You'll need to make an authentication provider that can handle that type of authenticationRequest:
import jcifs.Config;
import jcifs.UniAddress;
import jcifs.ntlmssp.NtlmMessage;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbSession;
import jcifs.util.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import javax.annotation.PostConstruct;
import java.io.IOException;
/**
* User: gcermak
* Date: 3/15/11
* <p/>
*/
public class ActiveDirectoryNTLMAuthenticationProvider implements AuthenticationProvider, InitializingBean {
protected String defaultDomain;
protected String domainController;
protected UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
public ActiveDirectoryNTLMAuthenticationProvider(){
Config.setProperty( "jcifs.smb.client.soTimeout", "1800000" );
Config.setProperty( "jcifs.netbios.cachePolicy", "1200" );
Config.setProperty( "jcifs.smb.lmCompatibility", "0" );
Config.setProperty( "jcifs.smb.client.useExtendedSecurity", "false" );
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
NTLMServiceRequestToken auth = (NTLMServiceRequestToken) authentication;
byte[] token = auth.getToken();
String name = null;
String password = null;
NtlmMessage message = constructNTLMMessage(token);
if (message instanceof Type1Message) {
Type2Message type2msg = null;
try {
type2msg = new Type2Message(new Type1Message(token), getChallenge(), null);
throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray()));
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
if (message instanceof Type3Message) {
final Type3Message type3msg;
try {
type3msg = new Type3Message(token);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0];
final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0];
NtlmPasswordAuthentication ntlmPasswordAuthentication = new NtlmPasswordAuthentication(type3msg.getDomain(), type3msg.getUser(), getChallenge(), lmResponse, ntResponse);
String username = ntlmPasswordAuthentication.getUsername();
String domain = ntlmPasswordAuthentication.getDomain();
String workstation = type3msg.getWorkstation();
name = ntlmPasswordAuthentication.getName();
password = ntlmPasswordAuthentication.getPassword();
}
// do custom logic here to find the user ...
userDetailsChecker.check(user);
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
// The Client will only ever send a Type1 or Type3 message ... try 'em both
protected static NtlmMessage constructNTLMMessage(byte[] token) {
NtlmMessage message = null;
try {
message = new Type1Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
try {
message = new Type3Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
return message;
}
protected byte[] getChallenge() {
UniAddress dcAddress = null;
try {
dcAddress = UniAddress.getByName(domainController, true);
return SmbSession.getChallenge(dcAddress);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
#Override
public boolean supports(Class<? extends Object> auth) {
return NTLMServiceRequestToken.class.isAssignableFrom(auth);
}
#Override
public void afterPropertiesSet() throws Exception {
// do nothing
}
public void setSmbClientUsername(String smbClientUsername) {
Config.setProperty("jcifs.smb.client.username", smbClientUsername);
}
public void setSmbClientPassword(String smbClientPassword) {
Config.setProperty("jcifs.smb.client.password", smbClientPassword);
}
public void setDefaultDomain(String defaultDomain) {
this.defaultDomain = defaultDomain;
Config.setProperty("jcifs.smb.client.domain", defaultDomain);
}
/**
* 0: Nothing
* 1: Critical [default]
* 2: Basic info. (Can be logged under load)
* 3: Detailed info. (Highest recommended level for production use)
* 4: Individual smb messages
* 6: Hex dumps
* #param logLevel the desired logging level
*/
public void setDebugLevel(int logLevel) throws Exception {
switch(logLevel) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 6:
Config.setProperty("jcifs.util.loglevel", Integer.toString(logLevel));
break;
default:
throw new Exception("Invalid Log Level specified");
}
}
/**
*
* #param winsList a comma separates list of wins addresses (ex. 10.169.10.77,10.169.10.66)
*/
public void setNetBiosWins(String winsList) {
Config.setProperty("jcifs.netbios.wins", winsList);
}
public void setDomainController(String domainController) {
this.domainController = domainController;
}
}
And finally you need to tie it all together in your spring_security.xml file:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http auto-config="true" use-expressions="true" disable-url-rewriting="true">
<form-login login-page="/auth/login"
login-processing-url="/auth/j_security_check"/>
<remember-me services-ref="rememberMeServices"/>
<logout invalidate-session="true" logout-success-url="/auth/logoutMessage" logout-url="/auth/logout"/>
<access-denied-handler error-page="/error/accessDenied"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUsernamePasswordUserDetailsService">
<password-encoder ref="passwordEncoder">
<salt-source ref="saltSource"/>
</password-encoder>
</authentication-provider>
<authentication-provider ref="NTLMAuthenticationProvider"/>
</authentication-manager>
</beans:beans>
Lastly you need to know how to tie it all together. The protocol as described in the first set of links shows that there are a couple round trips that you need to make between the client and server. Thus in your filter you need a bit more logic:
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.codec.Base64;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* User: gcermak
* Date: 12/5/11
*/
public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
// older versions of ie will sometimes do this
// logic cribbed from jcifs filter implementation jcifs.http.NtlmHttpFilter
if (request.getMethod().equalsIgnoreCase("POST")) {
if (decodedToken[8] == 1) {
logger.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage...");
Type1Message type1 = new Type1Message(decodedToken);
// respond with a type 2 message, where the challenge is null since we don't
// care about the server response (type-3 message) since we're already authenticated
// (This is just a by-pass - see method javadoc)
Type2Message type2 = new Type2Message(type1, new byte[8], null);
String msg = jcifs.util.Base64.encode(type2.toByteArray());
response.setHeader("WWW-Authenticate", "Negotiate " + msg);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
}
}
Authentication authenticationRequest = null;
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
Authentication authentication;
try {
authentication = authenticationManager.authenticate(authenticationRequest);
} catch (NtlmBaseException e) {
// this happens during the normal course of action of an NTLM authentication
// a type 2 message is the proper response to a type 1 message from the client
// see: http://www.innovation.ch/personal/ronald/ntlm.html
response.setHeader("WWW-Authenticate", e.getMessage());
response.setHeader("Connection", "Keep-Alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
} catch (AuthenticationException e) {
// That shouldn't happen, as it is most likely a wrong configuration on the server side
logger.warn("Negotiate Header was invalid: " + header, e);
SecurityContextHolder.clearContext();
if (failureHandler != null) {
failureHandler.onAuthenticationFailure(request, response, e);
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.flushBuffer();
}
return;
}
if (successHandler != null) {
successHandler.onAuthenticationSuccess(request, response, authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
#Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
}
You'll see that in the exception we use "Negotiate" rather than NTLM:
/**
* User: gcermak
* Date: 12/5/11
*/
public class NtlmType2MessageException extends NtlmBaseException {
private static final long serialVersionUID = 1L;
public NtlmType2MessageException(final String type2Msg) {
super("Negotiate " + type2Msg);
}
}
The spring filter (above) was largely patterned on jcifs.http.NtlmHttpFilter which you can find in the source for jcifs here:
http://jcifs.samba.org/
This isn't a whole, downloadable project as you requested but if there is interest from the community I could add this NTLM code to my github project:
http://git.springsource.org/~grantcermak/spring-security/activedirectory-se-security
Hope this helps!
Grant

Resources