Microsoft Remote Authentication
What tool can you login microsoft online services ?
今天要來講微軟的線上服務的登入,我想大家都知道微軟有套商用的office 365的線上服務(Software as a Service-SaaS),而一般使用者都是透過登入後,進而來使用或獲取微軟所提供的服務,而今天要討論的主題是要如何透過什麼方法可以讓程式開發者等技術人員,不經由一般的登入的方式,來取得微軟的提供的API服務 - Here some info about API接下來要來說明如何擷取微軟的遠端認證,認證分為3個步驟: 1.取得security token 2.取得access token 3.擷取出digest,一般來說只要做到第2個步驟就可以正常使用微軟提供的API了,大家可能會感到很熟悉,因為這邊要取得的token跟OAuth那邊蠻像的,都是認證方式,接下來開始介紹囉。
首先,先來講如何取得security token,首先我們要先準備一個xml的文件檔,如以下這樣:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
<o:Security s:mustUnderstand="1"
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken>
<o:Username>[username]</o:Username>
<o:Password>[password]</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>[endpoint(yourdomain.sharepoint.com/_api/web/)]</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
</t:RequestSecurityToken>
</s:Body>
</s:Envelope>
然後我們要使用http協定中的post方法將此段xml資料,貼在http 請求(request)裡面,並傳送到下面這個位置
https://login.microsoftonline.com/extSTS.srf
我用postman launcher來送這個http請求,一方面也是比較好觀察結果,因為這些驗證API的軟體有對回傳資料做分析(parse),並以顏色或各種強調的方式來顯示這些資訊,方便讓人一目瞭然,如下圖這樣。
然後會得到以下的回傳資料。
接下來我們要將, <wsse:BinarySecurityToken Id="Compact0">後面的t=一大串取出來(到結束標籤為止),這串就是我們要找的security token,接下來進入第2個步驟-取得access token,將這串token拿來在對下面的連結再做http post
https://yourdomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0
一般正常的話,會像下面這樣得到Set-Cookie欄位裡面有FedAuth跟rtFa,這2個cookie,有了這兩個cookie就能存取微軟的REST API了。
但有些電腦不會這麼順利,可能會發生像下面這樣被伺服器拒絕的狀況(DENY),這時只好請大家用之前講的其他測API的工具,如postman rest client、advanced rest client等等 然後以下我試著用java把這些過程實作出來,這邊必須引入http components中的多個jar檔(將.class(編譯好的code)包(封裝)好的zip壓縮格式、其實就可以等同函數庫做使用,使用時,需再java檔開頭以import引入),這次引入的jar檔有3個,一個是java 使用者端(client)的http,這工具包可以用來模擬瀏覽器向伺服器端(server)發出request(請求)的行為,第2個是剛剛這包裡面有包含的httpcore,裡面已經有將http request跟http responese這2種code包好了,可以讓我們不用再自行撰寫那2個關於銜接協定的訊息傳送與接收的code,讓使用者端更容易使用,如此一來讓我們對http 協定(protocol)能更加深入了解,最後第3個lib則是common-lang 2.6 ,這個包可以讓我們將不同語系的字元使用正確的編碼顯示正確的訊息,一般使用UTF-8格式,也就是http回傳的資料 裡面的中文與日文可以正常顯示。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
public class GetApiData {
String rtFa ="";
String fedAuth = "";
public static void main(String[] args) throws ClientProtocolException, IOException, ParseException {
GetApiData getApiData = new GetApiData();
getApiData.getToken();
getApiData.GetApiResponse();
}
public void GetApiResponse() {
try (CloseableHttpClient httpclient = HttpClients.createDefault();) {
//创建httpget
HttpGet httpget = new HttpGet("https://yourdomain.sharepoint.com/_api/web/lists/getbytitle('文件')/files");
httpget.setHeader("Accept", "application/json;charset=UTF-8");
httpget.setHeader("Connection", "keep-alive");
httpget.setHeader("Cookie",rtFa + ";" + fedAuth);
httpget.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36)");
System.out.println();
System.out.println("executing request " + httpget.getURI());
//执行get请求
try (CloseableHttpResponse response = httpclient.execute(httpget);) {
//獲取Repsonse 並以實體
HttpEntity entity = response.getEntity();
//Response狀態
System.out.println(response.getStatusLine());
if(entity != null) {
//Repsonse內容長度
System.out.println("response length: " + entity.getContentLength());
//Response內容
System.out.println("response content: " + StringEscapeUtils.unescapeJava(EntityUtils.toString(entity)).replace("{", "{\n").replace(",", ",\n").replace("}", "\n}") );//.replace("{", "{\n").replace(",", ",\n").replace("}", "\n}") );
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void getToken() throws ClientProtocolException, IOException
{
String xml = "";
String setCookie = "";
try(BufferedReader br = new BufferedReader(new FileReader("src/SecurityToken.xml")))
{
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while(line != null)
{
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
xml = sb.toString();
}
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost("https://login.microsoftonline.com/extSTS.srf");
HttpEntity entity = new ByteArrayEntity(xml.getBytes("UTF-8"));
post.setEntity(entity);
HttpResponse response = client.execute(post);
String responseContent = EntityUtils.toString(response.getEntity());
post = new HttpPost("https://yourdomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0");
entity = new ByteArrayEntity(extractToken(responseContent).getBytes());
post.setEntity(entity);
response = client.execute(post);
String entity2 = EntityUtils.toString(response.getEntity());
//get all headers
Header[] headers = response.getAllHeaders();
for (Header header : headers) {
System.out.println("Key : " + header.getName()
+ " ,Value : " + header.getValue());
if(header.getName().equals("Set-Cookie"))
{
setCookie += header.getValue() + " ";
}
}
System.out.println();
System.out.println(setCookie);
getCookies(setCookie);
}
protected String extractToken(String xml)
{
Pattern pattern = Pattern.compile("<wsse:BinarySecurityToken Id=\"Compact0\">(.+)</wsse:BinarySecurityToken>");
Matcher matcher = pattern.matcher(xml);
String securityToken = "";
while (matcher.find())
{
securityToken = matcher.group(1);
}
System.out.println(securityToken);
System.out.println();
return securityToken;
}
private void getCookies(String setCookie)
{
Pattern pattern = Pattern.compile("(.+); domain");
Matcher matcher = pattern.matcher(setCookie);
while (matcher.find())
{
rtFa = matcher.group(1);
}
System.out.println(rtFa);
pattern = Pattern.compile("FedAuth(.+)==");
matcher = pattern.matcher(setCookie);
while (matcher.find())
{
fedAuth = matcher.group();
}
System.out.println(fedAuth);
}
}
至於第3步驟,之後會再補充,謝謝大家。
**P.S. / Reference: 【java】微博爬虫(二):如何抓取HTML页面及HttpClient使用
SharePoint Online 遠端認證