OAuth 2.0 Assertion Flow with Office 365 Authorization Server
- Download Gradle Software
Gradle All
- Setup Environment Variable and Path
Define GRADLE_HOME Directory
Setup the PATH for gradle. For Example
Set GRADLE_HOME=c:\Gradle-1.5
Set PATH= %GRADLE_HOME%\bin;%PATH%
Set the Java Home Directory. For example set JAVA_HOME="c:\Progra~1\Java\jdk1.6.0_31".
- Creating the New App Principal Name
Build the Gradle Build File (build.gradle)
apply plugin : 'eclipse'
apply plugin : 'application'
sourceCompatibility = 1.6
group = 'oauth.sample.office365.client'
version = '1.0'
repositories {
mavenCentral()
}
dependencies{
compile 'org.apache.httpcomponents:httpclient:4.2.2','com.google.code.gson:gson:2.2.2','org.slf4j:log4j-over-slf4j:1.7.5'
}
mkdir oauth
mkdir oauth\office365oauth
mkdir oauth\office365oauth\src
mkdir oauth\office365oauth\src\main
mkdir oauth\office365oauth\src\main\java
mkdir oauth\office365oauth\src\main\resources
Copy the gradle.build file to c:\oauth\office365oauth
Execute the gradle cleanEclipse eclipse command and it will create the eclipse project.
Click Brose Button And Choose the Eclipse Project Directory. In Our Case C:\oauth\Office365oauth project
After Choosing the Eclipse Project, Please Click Ok Button and it will display the following screen.
Click Finish button and It will Import the eclipse project into the workspace. The sample imported eclipse screen shot given below.
Build the OAuth Test Client Devided into 2 parts. They are
a. Build Assertion
b. Send Request to Authorization Server to Get The Access Token
b. Send Request to Authorization Server to Get The Access Token
Build Assertion
Assertion need to be encoded with base64 format. The following sequence build the assertion. They are
Building Assertion Header
The assertion header parameters are typ and alg. The typ is Header type is JWT (JSON Web Token) and alg is algorithm is HS256 (Hash Sha 256). The Sample Code is given below.
Map <String,String> header= new HashMap<String,String>()
header.put("typ","JWT");
header.put("alg","HS256");
Convert the Assertion Header to JSON Object
String strHeader==gson.toJson(header);
Convert the JSON Object to Encode Base 64 Object
String strEnodeHeader=Base64.encodeBase64String(strHeader.getBytes("UTF8"));
Assertion Header Completed. Next Build the Assertion Pay Load.
Build Assertion Pay Load
The assertion payload parameters are iss, aud, nbf, and exp.
The iss is issuer. The issuer is the application principal id @ Microsoft Office 365 Tenant.
The aud is audience and the format of the aud is URI. The Sample aud is ACS Principal ID / Authorization Host @ Microsoft Office 365 Tenant.
The nbf is Not Before. The sample value is Current Date in seconds.
The exp is expired. The expiration of Access Token is in seconds.
The Sample code of the PayLoad is given below.
Map<String,String> payload= new HashMap<String,String>();
payload.put("iss",AppPrincipalName+"@"+"Microsoft Office 365 Tenant Name");
payload.put("aud",ACSPrincipalID+"/"+"Authorizer Server Host"+"@"+"Microsoft Office 365 Tenant Name");
payload.put("nbf",""+(new Date().getTime()/1000));
payload.put("exp",""+(addSecondsToCurrentTime(3600).getTime()/1000));
Convert the PayLoad Object to JSon Object
payload.put("iss",AppPrincipalName+"@"+"Microsoft Office 365 Tenant Name");
payload.put("aud",ACSPrincipalID+"/"+"Authorizer Server Host"+"@"+"Microsoft Office 365 Tenant Name");
payload.put("nbf",""+(new Date().getTime()/1000));
payload.put("exp",""+(addSecondsToCurrentTime(3600).getTime()/1000));
Convert the PayLoad Object to JSon Object
String strPayLoad= gson.toJson(payload);
Encode PayLoad JSON Obectwith Base 64
Encode PayLoad JSON Obectwith Base 64
String strEnodepayLoad=Base64.encodeBase64String(strPayLoad.getBytes("UTF8"));
Assertion Pay Load Completed. Next Combine the Assertion Header Encode with Assertion Payload Encode
Append Assertion Header Encode with Assertion Payload Encode
Assertion Pay Load Completed. Next Combine the Assertion Header Encode with Assertion Payload Encode
Append Assertion Header Encode with Assertion Payload Encode
As per the JWT specification the assertion encode header and assertion payload header append with dot. The Code is given below.
String rawToken= strEnodeHeader + "." +strPayLoad;
Sign the Raw Token
As per the the JWT specification, the raw token need to be signed with HmacSHA256 with secret key of the Application user Principal Name. This secret key is generated while creating the application principal name using the power shell. The sample Code is given below.
String rawToken= strEnodeHeader + "." +strPayLoad;
Sign the Raw Token
As per the the JWT specification, the raw token need to be signed with HmacSHA256 with secret key of the Application user Principal Name. This secret key is generated while creating the application principal name using the power shell. The sample Code is given below.
SecretKeySpec secretKey = null; secretKey = new SecretKeySpec(Base64.decodeBase64(signingKey), "HmacSHA256");
Mac mac; byte[] signedData = null; try{ mac = Mac.getInstance("HmacSHA256"); mac.init(secretKey); mac.update(rawToken.getBytes("UTF-8")); signedData = mac.doFinal(); } catch (Exception e) { e.printStackTrace(); }
Build The Signature
As per the JWT specification, the signed token needs to be encoded with base 64. The sample Code is given below
String signature = Base64.encodeBase64String(signedData);
Encode Signatre and RawToken
As per the JWT specification the raw token and signature append with dot and encode with base64 format. The Code is given below.
String assertion = String.format("%s.%s", rawToken, signature);
Assertion Building Completed.
Send Request to Authorization Server to Get The Access Token
Build the Post Request
The Authorization server url is required to construct the pst request. The sample code is given below.
DefaultHttpClient client= new DefaultHttpClient();
HttpPost post= new HttpPost("https://accounts.accesscontrol.windows.net/tokens/OAuth/2");
List <NameValuePair> nvps = new ArrayList<NameValuePair>();
Add the Request Parameters to the Post Request
The resource format is uri and Protected Resource Principal Id is 00000002-0000-0000-c000-000000000000 / Protected Resource Host Name is graph.windows.net @ Microsoft Office 365 Tenant.
String resource=String.format("%s/%s@%s", "00000002-0000-0000-c000-000000000000", "graph.windows.net","Microsoft Tenant Name");
nvps.add(new BasicNameValuePair("grant_type", "http://oauth.net/grant_type/jwt/1.0/bearer"));
nvps.add(new BasicNameValuePair("assertion", assertion));
nvps.add(new BasicNameValuePair("resource", resource));
Send Post Request
post.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8"));
HttpResponse response=client.execute(post);
Extract the Response
if(response.getStatusLine().getStatusCode() == 200){
HttpEntity entity=response.getEntity();
if (entity != null)
{
String retresponse=handler.handleResponse(response);
The response format is JSON and response attributes are token_type, access_token, expires_in, not_before, expires_on, resource
}
Example Code
package oauth.sample.office365.client; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import oauth.sample.office365.OauthToken; import org.apache.commons.codec.binary.Base64; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import com.google.gson.Gson; public class TestOAuthClient { private static String ALGORITHM="HS256"; private static String TYPE="JWT"; private static String APP_PRINCIPAL_ID="<Replace with App Service Principal ID>"; private static String ACS_PRINCIPAL_ID="00000001-0000-0000-c000-000000000000"; private static String AUTHRIZER_SERVER_HOST="accounts.accesscontrol.windows.net"; private static String DOMAIN_CONTEXT_ID="Replace with MicroSoft Office 365 Tenant Name"; private static String SYMMETRIC_KEY="Replace with Secret Key"; private static String AUTHRIZER_SERVER_URL="https://accounts.accesscontrol.windows.net/tokens/OAuth/2"; private static String GRANT_TYPE_URL="http://oauth.net/grant_type/jwt/1.0/bearer"; private static String PROTECTED_RESOURCE_PRINCIPAL_ID="00000002-0000-0000-c000-000000000000"; private static String PROTECTED_RES_HOST_NAME="graph.windows.net"; private static Gson gson= new Gson(); public TestOAuthClient() { // TODO Auto-generated constructor stub } public static void main(String args[]) throws Exception { String assertion=buildAssertion(); // Initializing the Http Connection DefaultHttpClient client= new DefaultHttpClient(); HttpPost post= new HttpPost(AUTHRIZER_SERVER_URL); List <NameValuePair> nvps = new ArrayList<NameValuePair>(); String resource=String.format("%s/%s@%s", PROTECTED_RESOURCE_PRINCIPAL_ID, PROTECTED_RES_HOST_NAME,DOMAIN_CONTEXT_ID); nvps.add(new BasicNameValuePair("grant_type", GRANT_TYPE_URL)); nvps.add(new BasicNameValuePair("assertion", assertion)); nvps.add(new BasicNameValuePair("resource", resource)); post.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8")); HttpResponse response=client.execute(post); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); System.out.println(response.getProtocolVersion()); if(response.getStatusLine().getStatusCode() == 200) { diplayHeaders(response,"authenticate"); HttpEntity entity=response.getEntity(); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); BasicResponseHandler handler= new BasicResponseHandler(); String retresponse=handler.handleResponse(response); OauthToken token =gson.fromJson(retresponse, OauthToken.class); System.out.println(retresponse); System.out.println("Exire On " +token.getExpires_on()); System.out.println("Exire in " +token.getExpires_in()); System.out.println("Exire not Before " +new Date(token.getNot_before()*1000)); System.out.println("Current Time " +new Date().getTime()); System.out.println("Expire Date "+ addSecondsToCurrentTime((int)token.getExpires_in()) + " - "+token.getExpires_on()); EntityUtils.consume(entity); } } else { diplayHeaders(response,"authenticate"); //throw new Office365Exception("Authentication Token Generation Error "+config.getAuthUrl()); } } public static void diplayHeaders(HttpResponse response,String methodName) { Header headers[]= response.getAllHeaders(); for (Header header : headers) { System.out.println(methodName+ " - Headers - "+header.getName() + " - "+header.getValue()); } } public static String buildAssertion() { String result=""; // start Assertion // Building Header Map<String, String> headerMap= new HashMap<String, String>(); headerMap.put("alg", ALGORITHM); headerMap.put("typ", TYPE); String strHeader=buildStringFromMap(headerMap); System.out.println(strHeader); Map<String, String> body= new HashMap<String,String>(); //format App Principal ID @ Domain Context ID body.put("iss", APP_PRINCIPAL_ID+"@"+DOMAIN_CONTEXT_ID); // Format ACS Principal Name / Authentication Host @ Domain Context ID body.put("aud", ACS_PRINCIPAL_ID+"/"+AUTHRIZER_SERVER_HOST+"@"+DOMAIN_CONTEXT_ID); body.put("nbf", ""+(new Date().getTime()/1000)); body.put("exp", ""+(addSecondsToCurrentTime(3600).getTime()/1000)); String strBody=buildStringFromMap(body); System.out.println(strBody); // Building Raw Token header + "." + Body String rawToken= strHeader + "."+ strBody; // sign the data using symmetric Key byte signedBytes[]= signData(SYMMETRIC_KEY, rawToken); //Create the Signature String signature = Base64.encodeBase64String(signedBytes); // Build the Access Token format is rawToken +"."+signature result = String.format("%s.%s", rawToken, signature); // End Assertion System.out.println(strHeader); return result; } private static byte[] signData(String signingKey, String rawToken) { SecretKeySpec secretKey = null; System.out.println("Finding Decode Key "+new String(Base64.decodeBase64(signingKey))); secretKey = new SecretKeySpec(Base64.decodeBase64(signingKey), "HmacSHA256"); Mac mac; byte[] signedData = null; try { mac = Mac.getInstance("HmacSHA256"); mac.init(secretKey); mac.update(rawToken.getBytes("UTF-8")); signedData = mac.doFinal(); } catch (Exception e) { e.printStackTrace(); } return signedData; } public static String buildStringFromMap(Map<String, String> data) { String result=""; if(!data.isEmpty()) { result=gson.toJson(data); try { result=Base64.encodeBase64String(result.getBytes("UTF8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return result; } public static Date addSecondsToCurrentTime(int seconds) { Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); System.out.println(calendar.getTime()); calendar.add(Calendar.SECOND, seconds); return calendar.getTime(); } }
package oauth.sample.office365;
public class OauthToken
{
private String token_type;
private String access_token;
private long expires_in;
private long not_before;
private long expires_on;
private String resource;
public String getToken_type() {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public long getExpires_in() {
return expires_in;
}
public void setExpires_in(long expires_in) {
this.expires_in = expires_in;
}
public long getNot_before() {
return not_before;
}
public void setNot_before(long not_before) {
this.not_before = not_before;
}
public long getExpires_on() {
return expires_on;
}
public void setExpires_on(long expires_on) {
this.expires_on = expires_on;
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
public OauthToken() {
// TODO Auto-generated constructor stub
}
}
Compile the Code Go to the Eclipse Project Directory command Prompt and run the gradle build command and It will compile. The compilation output as follows
C:\oauth\office365oauth>gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE
BUILD SUCCESSFUL
Execute the Test Client
Add the following content at the end of the line in the build.gradle file.
mainClassName="oauth.sample.office365.client.TestOAuthClient"
After Adding the above content , the build file shown like this. apply plugin : 'java' apply plugin : 'eclipse' apply plugin : 'application' sourceCompatibility = 1.6 group = 'oauth.sample.office365.client' version = '1.0' repositories { mavenCentral() } dependencies{ compile "org.apache.httpcomponents:httpclient:4.2.2", "com.google.code.gson:gson:2.2.2", "org.slf4j:log4j-over-slf4j:1.7.5" } mainClassName="oauth.sample.office365.client.TestOAuthClient"
After running the gradle run command the following output is shown.
C:\oauth\office365oauth>gradle run
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Wed May 01 18:33:17 PDT 2013
eyJleHAiOiIxMzY3NDYxOTk3IiwibmJmIjoiMTM2NzQ1ODM5NyIsImF1ZCI6IjAwMDAwMDAxLTAwMDAtMDAwMC1jMDAwLTAwMDAwMDAwMDAwMC9hY2NvdW50cy5hY2Nlc3Njb250cm9s
LndpbmRvd3MubmV0QHNmc3VvZmZpY2UzNjVkZXYub25taWNyb3NvZnQuY29tIiwiaXNzIjoiNGU5MGNmNDItNmM2MC00NzA2LTg1MDktZTM5YzM3NjViYzExQHNmc3VvZmZpY2UzNjVk
ZXYub25taWNyb3NvZnQuY29tIn0=
Finding Decode Key ò╟P↔°?Θ╣■:/£ôm┼zÖ6*9?←≥≥Q§←££0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
----------------------------------------
HTTP/1.1 200 OK
HTTP/1.1
authenticate - Headers - Cache-Control - no-cache, no-store
authenticate - Headers - Pragma - no-cache
authenticate - Headers - Content-Type - application/json; charset=utf-8
authenticate - Headers - Expires - -1
authenticate - Headers - request-id - 9ee87462-e08d-4ad2-af7a-2c50f47b6346
authenticate - Headers - X-Content-Type-Options - nosniff
authenticate - Headers - Date - Thu, 02 May 2013 01:34:14 GMT
authenticate - Headers - Content-Length - 1218
Response content length: 1218
{"token_type":"Bearer","access_token":"Access Token with Encoded string","expires_in":"43200","not_before":"1367458455","expires_on":"1367501655","resour
ce":"00000002-0000-0000-c000-000000000000/graph.windows.net@Microsoft Tenant ID"}
Exire On 1367501655
Exire in 43200
Exire not Before Wed May 01 18:34:15 PDT 2013
Current Time 1367458399228
Wed May 01 18:33:19 PDT 2013
Expire Date Thu May 02 06:33:19 PDT 2013 - 1367501655
BUILD SUCCESSFUL
References:
JWT Specification
Bearer Token Specification
Assertion Flow Specification
I have try this code and having problem with this code !
ReplyDeletewhat error your getting. please post the error and i will look the error and get back to you
Delete